使用 Lazy 实现 AsyncManualResetEvent 以确定是否已等待任务
本文关键字:是否 任务 等待 AsyncManualResetEvent Lazy 实现 使用 | 更新日期: 2023-09-27 18:33:29
我正在基于Stephen Toub的例子实现一个AsyncManualResetEvent。但是,我想知道该事件,或者特别是潜在的Task<T>
是否被等待。
我已经调查了Task
类,似乎没有一种明智的方法来确定它是否曾经被"等待"或是否添加了延续。
但是,在这种情况下,我控制对基础任务源的访问,因此我可以改为侦听对WaitAsync
方法的任何调用。在考虑如何做到这一点时,我决定使用Lazy<T>
,看看它是否已创建。
sealed class AsyncManualResetEvent {
public bool HasWaiters => tcs.IsValueCreated;
public AsyncManualResetEvent() {
Reset();
}
public Task WaitAsync() => tcs.Value.Task;
public void Set() {
if (tcs.IsValueCreated) {
tcs.Value.TrySetResult(result: true);
}
}
public void Reset() {
tcs = new Lazy<TaskCompletionSource<bool>>(LazyThreadSafetyMode.PublicationOnly);
}
Lazy<TaskCompletionSource<bool>> tcs;
}
那么我的问题是,这是否是一种安全的方法,特别是这是否保证在重置事件时永远不会有任何孤立/丢失的延续?
如果你真的想知道是否有人在你的任务上调用await
(不仅仅是他们打电话给WaitAsync()
的事实),你可以创建一个自定义等待者,充当m_tcs.Task
使用的TaskAwaiter
的包装器。
public class AsyncManualResetEvent
{
private volatile Completion _completion = new Completion();
public bool HasWaiters => _completion.HasWaiters;
public Completion WaitAsync()
{
return _completion;
}
public void Set()
{
_completion.Set();
}
public void Reset()
{
while (true)
{
var completion = _completion;
if (!completion.IsCompleted ||
Interlocked.CompareExchange(ref _completion, new Completion(), completion) == completion)
return;
}
}
}
public class Completion
{
private readonly TaskCompletionSource<bool> _tcs;
private readonly CompletionAwaiter _awaiter;
public Completion()
{
_tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
_awaiter = new CompletionAwaiter(_tcs.Task, this);
}
public CompletionAwaiter GetAwaiter() => _awaiter;
public bool IsCompleted => _tcs.Task.IsCompleted;
public bool HasWaiters { get; private set; }
public void Set() => _tcs.TrySetResult(true);
public struct CompletionAwaiter : ICriticalNotifyCompletion
{
private readonly TaskAwaiter _taskAwaiter;
private readonly Completion _parent;
internal CompletionAwaiter(Task task, Completion parent)
{
_parent = parent;
_taskAwaiter = task.GetAwaiter();
}
public bool IsCompleted => _taskAwaiter.IsCompleted;
public void GetResult() => _taskAwaiter.GetResult();
public void OnCompleted(Action continuation)
{
_parent.HasWaiters = true;
_taskAwaiter.OnCompleted(continuation);
}
public void UnsafeOnCompleted(Action continuation)
{
_parent.HasWaiters = true;
_taskAwaiter.UnsafeOnCompleted(continuation);
}
}
}
现在,如果有人注册了OnCompleted
或UnsafeOnCompleted
的延续,布尔HasWaiters
将变得true
。
我还添加了TaskCreationOptions.RunContinuationsAsynchronously
来修复 Stephen 在文章末尾使用Task.Factory.StartNew
修复的问题(在撰写本文后引入 .NET)。
如果你只是想看看是否有人叫WaitAsync,你可以简化它,你只需要一个类来保存你的标志和你的完成源。
public class AsyncManualResetEvent
{
private volatile CompletionWrapper _completionWrapper = new CompletionWrapper();
public Task WaitAsync()
{
var wrapper = _completionWrapper;
wrapper.WaitAsyncCalled = true;
return wrapper.Tcs.Task;
}
public bool WaitAsyncCalled
{
get { return _completionWrapper.WaitAsyncCalled; }
}
public void Set() {
_completionWrapper.Tcs.TrySetResult(true); }
public void Reset()
{
while (true)
{
var wrapper = _completionWrapper;
if (!wrapper.Tcs.Task.IsCompleted ||
Interlocked.CompareExchange(ref _completionWrapper, new CompletionWrapper(), wrapper) == wrapper)
return;
}
}
private class CompletionWrapper
{
public TaskCompletionSource<bool> Tcs { get; } = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
public bool WaitAsyncCalled { get; set; }
}
}