diff --git a/src/Nito.AsyncEx.Coordination/AsyncAutoResetEvent.cs b/src/Nito.AsyncEx.Coordination/AsyncAutoResetEvent.cs index 2f566a6..8cb68b7 100644 --- a/src/Nito.AsyncEx.Coordination/AsyncAutoResetEvent.cs +++ b/src/Nito.AsyncEx.Coordination/AsyncAutoResetEvent.cs @@ -47,17 +47,23 @@ public AsyncAutoResetEvent() /// Asynchronously waits for this event to be set. If the event is set, this method will auto-reset it and return immediately, even if the cancellation token is already signalled. If the wait is canceled, then it will not auto-reset this event. /// /// The cancellation token used to cancel this wait. - public Task WaitAsync(CancellationToken cancellationToken) + internal Task InternalWaitAsync(CancellationToken cancellationToken) { Task? result = null; InterlockedState.Transform(ref _state, s => s switch { { IsSet: true } => new State(false, s.Queue), - _ => new State(false, s.Queue.Enqueue(ApplyCancel, cancellationToken, out result)), + _ => new State(false, s.Queue.Enqueue(ApplyCancel, cancellationToken, out result)), }); - return result ?? Task.CompletedTask; + return result ?? Task.CompletedTask; } + /// + /// Asynchronously waits for this event to be set. If the event is set, this method will auto-reset it and return immediately, even if the cancellation token is already signalled. If the wait is canceled, then it will not auto-reset this event. + /// + /// The cancellation token used to cancel this wait. + public Task WaitAsync(CancellationToken cancellationToken) => AsyncUtility.ForceAsync(InternalWaitAsync(cancellationToken)); + /// /// Asynchronously waits for this event to be set. If the event is set, this method will auto-reset it and return immediately. /// @@ -72,7 +78,7 @@ public Task WaitAsync() /// The cancellation token used to cancel this wait. public void Wait(CancellationToken cancellationToken) { - WaitAsync(cancellationToken).WaitAndUnwrapException(CancellationToken.None); + InternalWaitAsync(cancellationToken).WaitAndUnwrapException(CancellationToken.None); } /// @@ -140,11 +146,11 @@ public DebugView(AsyncAutoResetEvent are) _are = are; } - public int Id { get { return _are.Id; } } + public int Id => _are.Id; - public bool IsSet { get { return _are._state.IsSet; } } + public bool IsSet => _are._state.IsSet; - public IAsyncWaitQueue WaitQueue { get { return _are._state.Queue; } } + public IAsyncWaitQueue WaitQueue => _are._state.Queue; } // ReSharper restore UnusedMember.Local } diff --git a/src/Nito.AsyncEx.Coordination/AsyncConditionVariable.cs b/src/Nito.AsyncEx.Coordination/AsyncConditionVariable.cs index 18f8d69..888cf9a 100644 --- a/src/Nito.AsyncEx.Coordination/AsyncConditionVariable.cs +++ b/src/Nito.AsyncEx.Coordination/AsyncConditionVariable.cs @@ -57,7 +57,7 @@ public void NotifyAll() /// Asynchronously waits for a signal on this condition variable. The associated lock MUST be held when calling this method, and it will still be held when this method returns, even if the method is cancelled. /// /// The cancellation signal used to cancel this wait. - public Task WaitAsync(CancellationToken cancellationToken) + internal Task InternalWaitAsync(CancellationToken cancellationToken) { Task task = null!; @@ -88,6 +88,12 @@ static async Task WaitAndRetakeLockAsync(Task task, AsyncLock asyncLock) } } + /// + /// Asynchronously waits for a signal on this condition variable. The associated lock MUST be held when calling this method, and it will still be held when this method returns, even if the method is cancelled. + /// + /// The cancellation signal used to cancel this wait. + public Task WaitAsync(CancellationToken cancellationToken) => AsyncUtility.ForceAsync(InternalWaitAsync(cancellationToken)); + /// /// Asynchronously waits for a signal on this condition variable. The associated lock MUST be held when calling this method, and it will still be held when the returned task completes. /// @@ -102,7 +108,7 @@ public Task WaitAsync() /// The cancellation signal used to cancel this wait. public void Wait(CancellationToken cancellationToken) { - WaitAsync(cancellationToken).WaitAndUnwrapException(CancellationToken.None); + InternalWaitAsync(cancellationToken).WaitAndUnwrapException(CancellationToken.None); } /// diff --git a/src/Nito.AsyncEx.Coordination/AsyncLock.cs b/src/Nito.AsyncEx.Coordination/AsyncLock.cs index 8721354..295ae39 100644 --- a/src/Nito.AsyncEx.Coordination/AsyncLock.cs +++ b/src/Nito.AsyncEx.Coordination/AsyncLock.cs @@ -65,7 +65,7 @@ public AsyncLock() /// /// The cancellation token used to cancel the lock. If this is already set, then this method will attempt to take the lock immediately (succeeding if the lock is currently available). /// A disposable that releases the lock when disposed. - private Task RequestLockAsync(CancellationToken cancellationToken) + internal Task InternalLockAsync(CancellationToken cancellationToken) { Task? result = null; InterlockedState.Transform(ref _state, s => s switch @@ -85,7 +85,7 @@ private Task RequestLockAsync(CancellationToken cancellationToken) /// A disposable that releases the lock when disposed. public AwaitableDisposable LockAsync(CancellationToken cancellationToken) { - return new AwaitableDisposable(RequestLockAsync(cancellationToken)); + return new AwaitableDisposable(AsyncUtility.ForceAsync(InternalLockAsync(cancellationToken))); } /// @@ -103,7 +103,7 @@ public AwaitableDisposable LockAsync() /// The cancellation token used to cancel the lock. If this is already set, then this method will attempt to take the lock immediately (succeeding if the lock is currently available). public IDisposable Lock(CancellationToken cancellationToken) { - return RequestLockAsync(cancellationToken).WaitAndUnwrapException(); + return InternalLockAsync(cancellationToken).WaitAndUnwrapException(); } /// diff --git a/src/Nito.AsyncEx.Coordination/AsyncReaderWriterLock.cs b/src/Nito.AsyncEx.Coordination/AsyncReaderWriterLock.cs index 16bd341..44aeb73 100644 --- a/src/Nito.AsyncEx.Coordination/AsyncReaderWriterLock.cs +++ b/src/Nito.AsyncEx.Coordination/AsyncReaderWriterLock.cs @@ -98,7 +98,7 @@ private void ReleaseWaitersWhenCanceled(Task task) /// /// The cancellation token used to cancel the lock. If this is already set, then this method will attempt to take the lock immediately (succeeding if the lock is currently available). /// A disposable that releases the lock when disposed. - private Task RequestReaderLockAsync(CancellationToken cancellationToken) + internal Task InternalReaderLockAsync(CancellationToken cancellationToken) { Task? task = null; InterlockedState.Transform(ref _state, s => s switch @@ -119,7 +119,7 @@ private Task RequestReaderLockAsync(CancellationToken cancellationT /// A disposable that releases the lock when disposed. public AwaitableDisposable ReaderLockAsync(CancellationToken cancellationToken) { - return new AwaitableDisposable(RequestReaderLockAsync(cancellationToken)); + return new AwaitableDisposable(AsyncUtility.ForceAsync(InternalReaderLockAsync(cancellationToken))); } /// @@ -138,7 +138,7 @@ public AwaitableDisposable ReaderLockAsync() /// A disposable that releases the lock when disposed. public IDisposable ReaderLock(CancellationToken cancellationToken) { - return RequestReaderLockAsync(cancellationToken).WaitAndUnwrapException(); + return InternalReaderLockAsync(cancellationToken).WaitAndUnwrapException(); } /// @@ -155,7 +155,7 @@ public IDisposable ReaderLock() /// /// The cancellation token used to cancel the lock. If this is already set, then this method will attempt to take the lock immediately (succeeding if the lock is currently available). /// A disposable that releases the lock when disposed. - private Task RequestWriterLockAsync(CancellationToken cancellationToken) + internal Task InternalWriterLockAsync(CancellationToken cancellationToken) { Task? task = null; InterlockedState.Transform(ref _state, s => s switch @@ -178,7 +178,7 @@ private Task RequestWriterLockAsync(CancellationToken cancellationT /// A disposable that releases the lock when disposed. public AwaitableDisposable WriterLockAsync(CancellationToken cancellationToken) { - return new AwaitableDisposable(RequestWriterLockAsync(cancellationToken)); + return new AwaitableDisposable(AsyncUtility.ForceAsync(InternalWriterLockAsync(cancellationToken))); } /// @@ -197,7 +197,7 @@ public AwaitableDisposable WriterLockAsync() /// A disposable that releases the lock when disposed. public IDisposable WriterLock(CancellationToken cancellationToken) { - return RequestWriterLockAsync(cancellationToken).WaitAndUnwrapException(); + return InternalWriterLockAsync(cancellationToken).WaitAndUnwrapException(); } /// diff --git a/src/Nito.AsyncEx.Coordination/AsyncSemaphore.cs b/src/Nito.AsyncEx.Coordination/AsyncSemaphore.cs index ea5b573..df9c980 100644 --- a/src/Nito.AsyncEx.Coordination/AsyncSemaphore.cs +++ b/src/Nito.AsyncEx.Coordination/AsyncSemaphore.cs @@ -36,7 +36,7 @@ public sealed class AsyncSemaphore /// Asynchronously waits for a slot in the semaphore to be available. /// /// The cancellation token used to cancel the wait. If this is already set, then this method will attempt to take the slot immediately (succeeding if a slot is currently available). - public Task WaitAsync(CancellationToken cancellationToken) + internal Task InternalWaitAsync(CancellationToken cancellationToken) { Task? result = null; InterlockedState.Transform(ref _state, s => s switch @@ -47,6 +47,12 @@ public Task WaitAsync(CancellationToken cancellationToken) return result ?? TaskConstants.Completed; } + /// + /// Asynchronously waits for a slot in the semaphore to be available. + /// + /// The cancellation token used to cancel the wait. If this is already set, then this method will attempt to take the slot immediately (succeeding if a slot is currently available). + public Task WaitAsync(CancellationToken cancellationToken) => AsyncUtility.ForceAsync(InternalWaitAsync(cancellationToken)); + /// /// Asynchronously waits for a slot in the semaphore to be available. /// @@ -61,7 +67,7 @@ public Task WaitAsync() /// The cancellation token used to cancel the wait. If this is already set, then this method will attempt to take the slot immediately (succeeding if a slot is currently available). public void Wait(CancellationToken cancellationToken) { - WaitAsync(cancellationToken).WaitAndUnwrapException(CancellationToken.None); + InternalWaitAsync(cancellationToken).WaitAndUnwrapException(CancellationToken.None); } /// diff --git a/src/Nito.AsyncEx.Coordination/Internals/AsyncUtility.cs b/src/Nito.AsyncEx.Coordination/Internals/AsyncUtility.cs new file mode 100644 index 0000000..1af6f86 --- /dev/null +++ b/src/Nito.AsyncEx.Coordination/Internals/AsyncUtility.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; + +namespace Nito.AsyncEx.Internals; + +internal static class AsyncUtility +{ + public static Task ForceAsync(Task task) + { + _ = task ?? throw new ArgumentNullException(nameof(task)); + return task.IsCompleted ? task : ForceAsyncCore(task); + + static async Task ForceAsyncCore(Task task) + { + await task.ConfigureAwait(false); + await Task.Yield(); + } + } + + public static Task ForceAsync(Task task) + { + _ = task ?? throw new ArgumentNullException(nameof(task)); + return task.IsCompleted ? task : ForceAsyncCore(task); + + static async Task ForceAsyncCore(Task task) + { + var result = await task.ConfigureAwait(false); + await Task.Yield(); + return result; + } + } +} \ No newline at end of file diff --git a/src/Nito.AsyncEx.Coordination/Nito.AsyncEx.Coordination.csproj b/src/Nito.AsyncEx.Coordination/Nito.AsyncEx.Coordination.csproj index 451c906..3c81c63 100644 --- a/src/Nito.AsyncEx.Coordination/Nito.AsyncEx.Coordination.csproj +++ b/src/Nito.AsyncEx.Coordination/Nito.AsyncEx.Coordination.csproj @@ -6,10 +6,6 @@ $(PackageTags);asynclock;asynclazy - - - -