Beispiel #1
0
 /// <summary>
 ///     Completes the oldest wait matching the specified <paramref name="key"/> with the specified <paramref name="result"/>.
 /// </summary>
 /// <typeparam name="T">The wait result type.</typeparam>
 /// <param name="key">The unique WaitKey for the wait.</param>
 /// <param name="result">The wait result.</param>
 public void Complete <T>(WaitKey key, T result)
 {
     if (Waits.TryGetValue(key, out var queue) && queue.TryDequeue(out var wait))
     {
         ((TaskCompletionSource <T>)wait.TaskCompletionSource).SetResult(result);
     }
 }
Beispiel #2
0
        /// <summary>
        ///     Adds a new wait for the specified <paramref name="key"/> and with the specified <paramref name="timeout"/>.
        /// </summary>
        /// <typeparam name="T">The wait result type.</typeparam>
        /// <param name="key">A unique WaitKey for the wait.</param>
        /// <param name="timeout">The wait timeout.</param>
        /// <param name="cancellationToken">The cancellation token for the wait.</param>
        /// <returns>A Task representing the wait.</returns>
        public Task <T> Wait <T>(WaitKey key, int?timeout = null, CancellationToken?cancellationToken = null)
        {
            timeout = timeout ?? DefaultTimeout;

            var wait = new PendingWait()
            {
                TaskCompletionSource = new TaskCompletionSource <T>(TaskCreationOptions.RunContinuationsAsynchronously),
                DateTime             = DateTime.UtcNow,
                TimeoutAfter         = (int)timeout,
                CancellationToken    = cancellationToken,
            };

            var recordLock = Locks.GetOrAdd(key, new ReaderWriterLockSlim());

            recordLock.EnterReadLock();

            try
            {
                Waits.AddOrUpdate(key, new ConcurrentQueue <PendingWait>(new[] { wait }), (_, queue) =>
                {
                    queue.Enqueue(wait);
                    return(queue);
                });
            }
            finally
            {
                recordLock.ExitReadLock();
            }

            return(((TaskCompletionSource <T>)wait.TaskCompletionSource).Task);
        }
Beispiel #3
0
 /// <summary>
 ///     Throws the specified <paramref name="exception"/> on the oldest wait matching the specified <paramref name="key"/>.
 /// </summary>
 /// <param name="key">The unique WaitKey for the wait.</param>
 /// <param name="exception">The Exception to throw.</param>
 public void Throw(WaitKey key, Exception exception)
 {
     if (Waits.TryGetValue(key, out var queue) && queue.TryDequeue(out var wait))
     {
         wait.TaskCompletionSource.SetException(exception);
     }
 }
Beispiel #4
0
        private void Disposition(WaitKey key, Action <PendingWait> action)
        {
            if (Waits.TryGetValue(key, out var queue) && queue.TryDequeue(out var wait))
            {
                action(wait);
                wait.Dispose();

                if (Locks.TryGetValue(key, out var recordLock))
                {
                    // enter a read lock first; TryPeek and TryDequeue are atomic so there's no risky operation until later.
                    recordLock.EnterUpgradeableReadLock();

                    try
                    {
                        // clean up entries in the Waits and Locks dictionaries if the corresponding ConcurrentQueue is empty.
                        // this is tricky, because we don't want to remove a record if another thread is in the process of
                        // enqueueing a new wait.
                        if (queue.IsEmpty)
                        {
                            // enter the write lock to prevent Wait() (which obtains a read lock) from enqueing any more waits
                            // before we can delete the dictionary record. it's ok and expected that Wait() might add this record
                            // back to the dictionary as soon as this unblocks; we're preventing new waits from being discarded if
                            // they are added by another thread just prior to the TryRemove() operation below.
                            recordLock.EnterWriteLock();

                            try
                            {
                                // check the queue again to ensure Wait() didn't enqueue anything between the last check and when
                                // we entered the write lock. this is guarateed to be safe since we now have exclusive access to
                                // the record and it should be impossible to remove a record containing a non-empty queue
                                if (queue.IsEmpty)
                                {
                                    Waits.TryRemove(key, out _);
                                    Locks.TryRemove(key, out _);
                                }
                            }
                            finally
                            {
                                recordLock.ExitWriteLock();
                            }
                        }
                    }
                    finally
                    {
                        recordLock.ExitUpgradeableReadLock();
                    }
                }
            }
        }
Beispiel #5
0
        /// <summary>
        ///     Adds a new wait for the specified <paramref name="key"/> and with the specified <paramref name="timeout"/>.
        /// </summary>
        /// <typeparam name="T">The wait result type.</typeparam>
        /// <param name="key">A unique WaitKey for the wait.</param>
        /// <param name="timeout">The wait timeout, in milliseconds.</param>
        /// <param name="cancellationToken">The cancellation token for the wait.</param>
        /// <returns>A Task representing the wait.</returns>
        public Task <T> Wait <T>(WaitKey key, int?timeout = null, CancellationToken?cancellationToken = null)
        {
            timeout ??= DefaultTimeout;
            cancellationToken ??= CancellationToken.None;

            var taskCompletionSource = new TaskCompletionSource <T>(TaskCreationOptions.RunContinuationsAsynchronously);

            var wait = new PendingWait(
                taskCompletionSource,
                timeout.Value,
                cancelAction: () => Cancel(key),
                timeoutAction: () => Timeout(key),
                cancellationToken.Value);

            // obtain a read lock for the key. this is necessary to prevent this code from adding a wait to the ConcurrentQueue
            // while the containing dictionary entry is being cleaned up in Disposition(), effectively discarding the new wait.
#pragma warning disable IDE0067, CA2000 // Dispose objects before losing scope
            var recordLock = Locks.GetOrAdd(key, new ReaderWriterLockSlim());
#pragma warning restore IDE0067, CA2000 // Dispose objects before losing scope

            recordLock.EnterReadLock();

            try
            {
                Waits.AddOrUpdate(key, new ConcurrentQueue <PendingWait>(new[] { wait }), (_, queue) =>
                {
                    queue.Enqueue(wait);
                    return(queue);
                });
            }
            finally
            {
                recordLock.ExitReadLock();
            }

            // defer registration to prevent the wait from being dispositioned prior to being successfully queued this is a
            // concern if we are given a timeout of 0, or a cancellation token which is already cancelled
            wait.Register();
            return(((TaskCompletionSource <T>)wait.TaskCompletionSource).Task);
        }
Beispiel #6
0
 /// <summary>
 ///     Adds a new wait for the specified <paramref name="key"/> which does not time out.
 /// </summary>
 /// <typeparam name="T">The wait result type.</typeparam>
 /// <param name="key">A unique WaitKey for the wait.</param>
 /// <param name="cancellationToken">The cancellation token for the wait.</param>
 /// <returns>A Task representing the wait.</returns>
 public Task <T> WaitIndefinitely <T>(WaitKey key, CancellationToken?cancellationToken = null)
 {
     return(Wait <T>(key, int.MaxValue, cancellationToken));
 }
Beispiel #7
0
 /// <summary>
 ///     Adds a new wait for the specified <paramref name="key"/> which does not time out.
 /// </summary>
 /// <param name="key">A unique WaitKey for the wait.</param>
 /// <param name="cancellationToken">The cancellation token for the wait.</param>
 /// <returns>A Task representing the wait.</returns>
 public Task WaitIndefinitely(WaitKey key, CancellationToken?cancellationToken = null)
 {
     return(WaitIndefinitely <object>(key, cancellationToken));
 }
Beispiel #8
0
 /// <summary>
 ///     Adds a new wait for the specified <paramref name="key"/> and with the specified <paramref name="timeout"/>.
 /// </summary>
 /// <param name="key">A unique WaitKey for the wait.</param>
 /// <param name="timeout">The wait timeout.</param>
 /// <param name="cancellationToken">The cancellation token for the wait.</param>
 /// <returns>A Task representing the wait.</returns>
 public Task Wait(WaitKey key, int?timeout = null, CancellationToken?cancellationToken = null)
 {
     return(Wait <object>(key, timeout, cancellationToken));
 }
Beispiel #9
0
 /// <summary>
 ///     Completes the oldest wait matching the specified <paramref name="key"/>.
 /// </summary>
 /// <param name="key">The unique WaitKey for the wait.</param>
 public void Complete(WaitKey key)
 {
     Complete <object>(key, null);
 }
Beispiel #10
0
 /// <summary>
 ///     Completes the oldest wait matching the specified <paramref name="key"/> with the specified <paramref name="result"/>.
 /// </summary>
 /// <typeparam name="T">The wait result type.</typeparam>
 /// <param name="key">The unique WaitKey for the wait.</param>
 /// <param name="result">The wait result.</param>
 public void Complete <T>(WaitKey key, T result)
 {
     Disposition(key, wait =>
                 ((TaskCompletionSource <T>)wait.TaskCompletionSource).TrySetResult(result));
 }
Beispiel #11
0
 /// <summary>
 ///     Cancels the oldest wait matching the specified <paramref name="key"/>.
 /// </summary>
 /// <param name="key">The unique WaitKey for the wait.</param>
 public void Cancel(WaitKey key)
 {
     Disposition(key, wait =>
                 wait.TaskCompletionSource.TrySetCanceled());
 }
Beispiel #12
0
 /// <summary>
 ///     Causes the oldest wait matching the specified <paramref name="key"/> to time out.
 /// </summary>
 /// <param name="key">The unique WaitKey for the wait.</param>
 public void Timeout(WaitKey key)
 {
     Disposition(key, wait =>
                 wait.TaskCompletionSource.TrySetException(new TimeoutException($"The wait timed out after {wait.Timeout} milliseconds")));
 }
Beispiel #13
0
 /// <summary>
 ///     Throws the specified <paramref name="exception"/> on the oldest wait matching the specified <paramref name="key"/>.
 /// </summary>
 /// <param name="key">The unique WaitKey for the wait.</param>
 /// <param name="exception">The Exception to throw.</param>
 public void Throw(WaitKey key, Exception exception)
 {
     Disposition(key, wait =>
                 wait.TaskCompletionSource.TrySetException(exception));
 }