using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace MQTTnet.Internal { // Inspired from Stephen Toub (https://blogs.msdn.microsoft.com/pfxteam/2012/02/11/building-async-coordination-primitives-part-2-asyncautoresetevent/) and Chris Gillum (https://stackoverflow.com/a/43012490) public class AsyncAutoResetEvent { private readonly LinkedList> _waiters = new LinkedList>(); private bool _isSignaled; public AsyncAutoResetEvent() : this(false) { } public AsyncAutoResetEvent(bool signaled) { _isSignaled = signaled; } public Task WaitOneAsync() { return WaitOneAsync(CancellationToken.None); } public Task WaitOneAsync(TimeSpan timeout) { return WaitOneAsync(timeout, CancellationToken.None); } public Task WaitOneAsync(CancellationToken cancellationToken) { return WaitOneAsync(Timeout.InfiniteTimeSpan, cancellationToken); } public async Task WaitOneAsync(TimeSpan timeout, CancellationToken cancellationToken) { cancellationToken.ThrowIfCancellationRequested(); TaskCompletionSource tcs; lock (_waiters) { if (_isSignaled) { _isSignaled = false; return true; } else if (timeout == TimeSpan.Zero) { return _isSignaled; } else { tcs = new TaskCompletionSource(); _waiters.AddLast(tcs); } } Task winner = await Task.WhenAny(tcs.Task, Task.Delay(timeout, cancellationToken)).ConfigureAwait(false); if (winner == tcs.Task) { // The task was signaled. return true; } else { // We timed-out; remove our reference to the task. // This is an O(n) operation since waiters is a LinkedList. lock (_waiters) { _waiters.Remove(tcs); if (winner.Status == TaskStatus.Canceled) { throw new OperationCanceledException(cancellationToken); } else { throw new TimeoutException(); } } } } public void Set() { TaskCompletionSource toRelease = null; lock (_waiters) { if (_waiters.Count > 0) { // Signal the first task in the waiters list. toRelease = _waiters.First.Value; _waiters.RemoveFirst(); } else if (!_isSignaled) { // No tasks are pending _isSignaled = true; } } toRelease?.SetResult(true); } } }