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

Implement AsyncManualResetEvent using Lazy<T> to determine if the task has been awaited

我正在实施基于 Stephen Toub's example 的 AsyncManualResetEvent。但是,我想知道事件,或者具体来说,底层 Task<T> 是否已经等待。

我已经调查了 Task class,但似乎没有一种明智的方法来确定它是否曾经 'awaited' 或者是否有延续已添加。

然而,在这种情况下,我控制对底层任务源的访问,因此我可以监听对 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;
}

那么我的问题是,这是否是一种安全的方法,具体来说,这是否可以保证在重置事件时永远不会有任何 orphaned/lost 继续?

如果您真的想知道是否有人在您的任务中调用了 await(而不仅仅是他们调用了 WaitAsync() 的事实),您可以制作一个自定义等待程序作为 await 的包装器=14=] 被 m_tcs.Task.

使用
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来解决斯蒂芬在文章末尾用Task.Factory.StartNew修复的问题(它是在文章写完后引入.NET的)。


如果你只是想看看是否有人调用 WaitAsync 你可以简化它很多,你只需要一个 class 来保存你的标志和你的完成源。

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