/// <summary>Performs the asynchronous wait.</summary> /// <param name="asyncWaiter"></param> /// <param name="millisecondsTimeout">The timeout.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task to return to the caller.</returns> private async Task <bool> WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken) { Debug.Assert(asyncWaiter != null, "Waiter should have been constructed"); //Debug.Assert(m_lock.IsAcquired, "Requires the lock be held"); // Wait until either the task is completed, timeout occurs, or cancellation is requested. // We need to ensure that the Task.Delay task is appropriately cleaned up if the await // completes due to the asyncWaiter completing, so we use our own token that we can explicitly // cancel, and we chain the caller's supplied token into it. using (var cts = cancellationToken.CanBeCanceled ? CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, default(CancellationToken)) : new CancellationTokenSource()) { var waitCompleted = Task.WhenAny(asyncWaiter, Task.Delay(millisecondsTimeout, cts.Token)); if (asyncWaiter == await waitCompleted.ConfigureAwait(false)) { cts.Cancel(); // ensure that the Task.Delay task is cleaned up return(true); // successfully acquired } } // If we get here, the wait has timed out or been canceled. // If the await completed synchronously, we still hold the lock. If it didn't, // we no longer hold the lock. As such, acquire it. //using (LockHolder.Hold(m_lock)) lock (m_lockObject) { // Remove the task from the list. If we're successful in doing so, // we know that no one else has tried to complete this waiter yet, // so we can safely cancel or timeout. if (RemoveAsyncWaiter(asyncWaiter)) { cancellationToken.ThrowIfCancellationRequested(); // cancellation occurred return(false); // timeout occurred } } // The waiter had already been removed, which means it's already completed or is about to // complete, so let it, and don't return until it does. return(await asyncWaiter.ConfigureAwait(false)); }
/// <summary>Performs the asynchronous wait.</summary> /// <param name="asyncWaiter">The asynchronous waiter.</param> /// <param name="millisecondsTimeout">The timeout.</param> /// <param name="cancellationToken">The cancellation token.</param> /// <returns>The task to return to the caller.</returns> private async Task <bool> WaitUntilCountOrTimeoutAsync(TaskNode asyncWaiter, int millisecondsTimeout, CancellationToken cancellationToken) { Debug.Assert(asyncWaiter != null, "Waiter should have been constructed"); Debug.Assert(Monitor.IsEntered(m_lockObjAndDisposed), "Requires the lock be held"); await new ConfiguredNoThrowAwaiter <bool>(asyncWaiter.WaitAsync(TimeSpan.FromMilliseconds(millisecondsTimeout), cancellationToken)); if (cancellationToken.IsCancellationRequested) { // If we might be running as part of a cancellation callback, force the completion to be asynchronous // so as to maintain semantics similar to when no token is passed (neither Release nor Cancel would invoke // continuations off of this task). await TaskScheduler.Default; } if (asyncWaiter.IsCompleted) { return(true); // successfully acquired } // The wait has timed out or been canceled. // If the await completed synchronously, we still hold the lock. If it didn't, // we no longer hold the lock. As such, acquire it. lock (m_lockObjAndDisposed) { // Remove the task from the list. If we're successful in doing so, // we know that no one else has tried to complete this waiter yet, // so we can safely cancel or timeout. if (RemoveAsyncWaiter(asyncWaiter)) { cancellationToken.ThrowIfCancellationRequested(); // cancellation occurred return(false); // timeout occurred } } // The waiter had already been removed, which means it's already completed or is about to // complete, so let it, and don't return until it does. return(await asyncWaiter.ConfigureAwait(false)); }