using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace ZeroLevel.Services.Async { /// <summary> /// An async-compatible manual-reset event. /// </summary> [DebuggerDisplay("Id = {Id}, IsSet = {GetStateForDebugger}")] [DebuggerTypeProxy(typeof(DebugView))] public sealed class AsyncManualResetEvent { /// <summary> /// The object used for synchronization. /// </summary> private readonly object _mutex; /// <summary> /// The current state of the event. /// </summary> private TaskCompletionSource<object> _tcs; /// <summary> /// The semi-unique identifier for this instance. This is 0 if the id has not yet been created. /// </summary> private int _id; [DebuggerNonUserCode] private bool GetStateForDebugger { get { return _tcs.Task.IsCompleted; } } /// <summary> /// Creates an async-compatible manual-reset event. /// </summary> /// <param name="set">Whether the manual-reset event is initially set or unset.</param> public AsyncManualResetEvent(bool set) { _mutex = new object(); _tcs = TaskCompletionSourceExtensions.CreateAsyncTaskSource<object>(); if (set) _tcs.TrySetResult(null); } /// <summary> /// Creates an async-compatible manual-reset event that is initially unset. /// </summary> public AsyncManualResetEvent() : this(false) { } /// <summary> /// Gets a semi-unique identifier for this asynchronous manual-reset event. /// </summary> public int Id { get { return IdManager<AsyncManualResetEvent>.GetId(ref _id); } } /// <summary> /// Whether this event is currently set. This member is seldom used; code using this member has a high possibility of race conditions. /// </summary> public bool IsSet { get { lock (_mutex) return _tcs.Task.IsCompleted; } } /// <summary> /// Asynchronously waits for this event to be set. /// </summary> public Task WaitAsync() { lock (_mutex) { return _tcs.Task; } } /// <summary> /// Asynchronously waits for this event to be set or for the wait to be canceled. /// </summary> /// <param name="cancellationToken">The cancellation token used to cancel the wait. If this token is already canceled, this method will first check whether the event is set.</param> public Task WaitAsync(CancellationToken cancellationToken) { var waitTask = WaitAsync(); if (waitTask.IsCompleted) return waitTask; return waitTask.WaitAsync(cancellationToken); } /// <summary> /// Synchronously waits for this event to be set. This method may block the calling thread. /// </summary> public void Wait() { WaitAsync().WaitAndUnwrapException(); } /// <summary> /// Synchronously waits for this event to be set. This method may block the calling thread. /// </summary> /// <param name="cancellationToken">The cancellation token used to cancel the wait. If this token is already canceled, this method will first check whether the event is set.</param> public void Wait(CancellationToken cancellationToken) { var ret = WaitAsync(); if (ret.IsCompleted) return; ret.WaitAndUnwrapException(cancellationToken); } /// <summary> /// Sets the event, atomically completing every task returned by <see cref="O:Nito.AsyncEx.AsyncManualResetEvent.WaitAsync"/>. If the event is already set, this method does nothing. /// </summary> public void Set() { lock (_mutex) { _tcs.TrySetResult(null); } } /// <summary> /// Resets the event. If the event is already reset, this method does nothing. /// </summary> public void Reset() { lock (_mutex) { if (_tcs.Task.IsCompleted) _tcs = TaskCompletionSourceExtensions.CreateAsyncTaskSource<object>(); } } // ReSharper disable UnusedMember.Local [DebuggerNonUserCode] private sealed class DebugView { private readonly AsyncManualResetEvent _mre; public DebugView(AsyncManualResetEvent mre) { _mre = mre; } public int Id { get { return _mre.Id; } } public bool IsSet { get { return _mre.GetStateForDebugger; } } public Task CurrentTask { get { return _mre._tcs.Task; } } } // ReSharper restore UnusedMember.Local } }