forked from microsoft/vs-threading
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathNonConcurrentSynchronizationContext.cs
More file actions
177 lines (158 loc) · 6.98 KB
/
Copy pathNonConcurrentSynchronizationContext.cs
File metadata and controls
177 lines (158 loc) · 6.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
namespace Microsoft.VisualStudio.Threading
{
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// A <see cref="SynchronizationContext"/> that executes messages in the order they are received.
/// </summary>
/// <remarks>
/// Delegates will be invoked in the order they are received on the threadpool.
/// No two delegates will ever be executed concurrently, but <see cref="Send(SendOrPostCallback, object?)"/> may permit
/// a delegate to execute inline on another.
/// Note that if the delegate invokes an async method, the delegate formally ends
/// when the async method yields for the first time or returns, whichever comes first.
/// Once that delegate returns the next delegate can be executed.
/// </remarks>
public sealed class NonConcurrentSynchronizationContext : SynchronizationContext
{
/// <summary>
/// The queue of work to execute, if this is the original instance.
/// </summary>
private readonly AsyncQueue<(SendOrPostCallback, object?)>? queue;
/// <summary>
/// A value indicating whether to set this instance as <see cref="SynchronizationContext.Current" /> when invoking delegates.
/// </summary>
private readonly bool sticky;
/// <summary>
/// The original instance (on which <see cref="CreateCopy"/> was called).
/// </summary>
private readonly NonConcurrentSynchronizationContext? copyOf;
/// <summary>
/// Set to the <see cref="Environment.CurrentManagedThreadId"/> when a delegate is currently executing.
/// </summary>
private int? activeManagedThreadId;
/// <summary>
/// Initializes a new instance of the <see cref="NonConcurrentSynchronizationContext"/> class.
/// </summary>
/// <param name="sticky">
/// A value indicating whether to set this instance as <see cref="SynchronizationContext.Current" /> when invoking delegates.
/// This has the effect that async methods that are invoked on this <see cref="SynchronizationContext" />
/// will execute their continuations on this <see cref="SynchronizationContext" /> as well unless they use <see cref="Task.ConfigureAwait(bool)" /> with <c>false</c> as the argument.
/// </param>
public NonConcurrentSynchronizationContext(bool sticky)
{
this.queue = new AsyncQueue<(SendOrPostCallback, object?)>();
this.sticky = sticky;
// Start the queue processor. It will handle all exceptions.
this.ProcessQueueAsync().Forget();
}
/// <summary>
/// Initializes a new instance of the <see cref="NonConcurrentSynchronizationContext"/> class
/// that is a copy of an existing instance.
/// </summary>
/// <param name="copyFrom">The instance to copy from.</param>
private NonConcurrentSynchronizationContext(NonConcurrentSynchronizationContext copyFrom)
{
// We do *not* kick off processing of the queue, since the original copy has done that and we don't want two logical threads processing the queue.
this.sticky = copyFrom.sticky;
this.copyOf = copyFrom;
}
/// <summary>
/// Occurs when posted work throws an unhandled exception.
/// </summary>
/// <remarks>
/// Any exception thrown from this handler will crash the process.
/// </remarks>
public event EventHandler<Exception>? UnhandledException;
/// <inheritdoc />
public override void Post(SendOrPostCallback d, object? state)
{
Requires.NotNull(d, nameof(d));
if (this.copyOf is object)
{
this.copyOf.Post(d, state);
return;
}
this.queue!.Enqueue((d, state));
}
/// <inheritdoc />
public override void Send(SendOrPostCallback d, object? state)
{
Requires.NotNull(d, nameof(d));
if (this.copyOf is object)
{
this.copyOf.Send(d, state);
return;
}
// Allow inlining if we're on the already-assigned thread.
if (this.activeManagedThreadId is int activeThread && Environment.CurrentManagedThreadId == activeThread)
{
using (this.sticky ? this.Apply(checkForChangesOnRevert: false) : default)
{
d(state);
}
return;
}
var tcs = new TaskCompletionSource<object?>();
this.Post(
s2 =>
{
(SendOrPostCallback cb, object s, TaskCompletionSource<object?> m) = (Tuple<SendOrPostCallback, object, TaskCompletionSource<object?>>)s2!;
try
{
cb(s);
m.SetResult(null);
}
catch (Exception ex)
{
m.SetException(ex);
}
},
Tuple.Create(d, state, tcs));
tcs.Task.GetAwaiter().GetResult();
}
/// <inheritdoc />
public override SynchronizationContext CreateCopy() => new NonConcurrentSynchronizationContext(this);
/// <summary>
/// Executes queued work on the threadpool, one at a time.
/// </summary>
/// <returns>A task that always completes successfully.</returns>
private async Task ProcessQueueAsync()
{
try
{
while (true)
{
(SendOrPostCallback, object?) work = await this.queue!.DequeueAsync().ConfigureAwait(false);
this.activeManagedThreadId = Environment.CurrentManagedThreadId;
try
{
using (this.sticky ? this.Apply(checkForChangesOnRevert: false) : default)
{
work.Item1(work.Item2);
}
}
catch (Exception ex)
{
this.UnhandledException?.Invoke(this, ex);
}
finally
{
this.activeManagedThreadId = null;
}
}
}
catch (Exception ex)
{
// A failure to schedule work is fatal because it can lead to hangs that are
// very hard to diagnose to a failure in the scheduler, and even harder to identify
// the root cause of the failure in the scheduler.
Environment.FailFast("Failure in scheduler.", ex);
}
}
}
}