使用 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;
}

那么我的问题是,这是否是一种安全的方法,特别是这是否保证在重置事件时永远不会有任何孤立/丢失的延续?

使用 Lazy<T> 实现 AsyncManualResetEvent 以确定是否已等待任务

如果你真的想知道是否有人在你的任务上调用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);
        }
    }
}

现在,如果有人注册了OnCompletedUnsafeOnCompleted的延续,布尔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; }
    }
}