forked from microsoft/vs-threading
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAsyncAutoResetEvent.cs
More file actions
191 lines (173 loc) · 7.43 KB
/
Copy pathAsyncAutoResetEvent.cs
File metadata and controls
191 lines (173 loc) · 7.43 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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// 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.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// An asynchronous implementation of an AutoResetEvent.
/// </summary>
[DebuggerDisplay("Signaled: {signaled}")]
public class AsyncAutoResetEvent
{
/// <summary>
/// A queue of folks awaiting signals.
/// </summary>
private readonly Queue<WaiterCompletionSource> signalAwaiters = new Queue<WaiterCompletionSource>();
/// <summary>
/// Whether to complete the task synchronously in the <see cref="Set"/> method,
/// as opposed to asynchronously.
/// </summary>
private readonly bool allowInliningAwaiters;
/// <summary>
/// A reusable delegate that points to the <see cref="OnCancellationRequest(object)"/> method.
/// </summary>
private readonly Action<object> onCancellationRequestHandler;
/// <summary>
/// A value indicating whether this event is already in a signaled state.
/// </summary>
/// <devremarks>
/// This should not need the volatile modifier because it is
/// always accessed within a lock.
/// </devremarks>
private bool signaled;
/// <summary>
/// Initializes a new instance of the <see cref="AsyncAutoResetEvent"/> class
/// that does not inline awaiters.
/// </summary>
public AsyncAutoResetEvent()
: this(allowInliningAwaiters: false)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="AsyncAutoResetEvent"/> class.
/// </summary>
/// <param name="allowInliningAwaiters">
/// A value indicating whether to complete the task synchronously in the <see cref="Set"/> method,
/// as opposed to asynchronously. <c>false</c> better simulates the behavior of the
/// <see cref="AutoResetEvent"/> class, but <c>true</c> can result in slightly better performance.
/// </param>
public AsyncAutoResetEvent(bool allowInliningAwaiters)
{
this.allowInliningAwaiters = allowInliningAwaiters;
this.onCancellationRequestHandler = this.OnCancellationRequest;
}
/// <summary>
/// Returns an awaitable that may be used to asynchronously acquire the next signal.
/// </summary>
/// <returns>An awaitable.</returns>
public Task WaitAsync()
{
return this.WaitAsync(CancellationToken.None);
}
/// <summary>
/// Returns an awaitable that may be used to asynchronously acquire the next signal.
/// </summary>
/// <param name="cancellationToken">A token whose cancellation removes the caller from the queue of those waiting for the event.</param>
/// <returns>An awaitable.</returns>
public Task WaitAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
lock (this.signalAwaiters)
{
if (this.signaled)
{
this.signaled = false;
return Task.CompletedTask;
}
else
{
var waiter = new WaiterCompletionSource(this, this.allowInliningAwaiters, cancellationToken);
if (cancellationToken.IsCancellationRequested)
{
waiter.TrySetCanceled(cancellationToken);
}
else
{
this.signalAwaiters.Enqueue(waiter);
}
return waiter.Task;
}
}
}
/// <summary>
/// Unblocks one waiter or sets the signal if no waiters are present so the next waiter may proceed immediately.
/// </summary>
public void Set()
{
WaiterCompletionSource? toRelease = null;
lock (this.signalAwaiters)
{
if (this.signalAwaiters.Count > 0)
{
toRelease = this.signalAwaiters.Dequeue();
}
else if (!this.signaled)
{
this.signaled = true;
}
}
if (toRelease is object)
{
toRelease.Registration.Dispose();
toRelease.TrySetResult(default(EmptyStruct));
}
}
/// <summary>
/// Responds to cancellation requests by removing the request from the waiter queue.
/// </summary>
/// <param name="state">The <see cref="WaiterCompletionSource"/> passed in to the <see cref="CancellationToken.Register(Action{object}, object)"/> method.</param>
private void OnCancellationRequest(object state)
{
var tcs = (WaiterCompletionSource)state;
bool removed;
lock (this.signalAwaiters)
{
removed = this.signalAwaiters.RemoveMidQueue(tcs);
}
// We only cancel the task if we removed it from the queue.
// If it wasn't in the queue, either it has already been signaled
// or it hasn't even been added to the queue yet. If the latter,
// the Task will be canceled later so long as the signal hasn't been awarded
// to this Task yet.
if (removed)
{
tcs.TrySetCanceled(tcs.CancellationToken);
}
}
/// <summary>
/// Tracks someone waiting for a signal from the event.
/// </summary>
private class WaiterCompletionSource : TaskCompletionSourceWithoutInlining<EmptyStruct>
{
/// <summary>
/// Initializes a new instance of the <see cref="WaiterCompletionSource"/> class.
/// </summary>
/// <param name="owner">The event that is initializing this value.</param>
/// <param name="allowInliningContinuations"><c>true</c> to allow continuations to be inlined upon the completer's callstack.</param>
/// <param name="cancellationToken">The cancellation token associated with the waiter.</param>
internal WaiterCompletionSource(AsyncAutoResetEvent owner, bool allowInliningContinuations, CancellationToken cancellationToken)
: base(allowInliningContinuations)
{
this.CancellationToken = cancellationToken;
this.Registration = cancellationToken.Register(NullableHelpers.AsNullableArgAction(owner.onCancellationRequestHandler), this);
}
/// <summary>
/// Gets the <see cref="CancellationToken"/> provided by the waiter.
/// </summary>
internal CancellationToken CancellationToken { get; private set; }
/// <summary>
/// Gets the registration to dispose of when the waiter receives their event.
/// </summary>
internal CancellationTokenRegistration Registration { get; private set; }
}
}
}