/// <summary> /// Obtains exclusive access to the lock. /// </summary> /// <returns> /// A task that must be awaited to obtain the token that will /// release the lock on <see cref="IDisposable.Dispose"/>. /// </returns> public async Task <IDisposable> EnterAsync() { LockEntry lockEntry = new LockEntry(); await Interlocked.Exchange(ref m_task, lockEntry.WhenReleased()).ConfigureAwait(false); return(lockEntry); }
/// <summary> /// Attempts to obtain exclusive access to the lock. /// </summary> /// <param name="timeout">The amount of time to wait before failing to take the lock.</param> /// <returns> /// A task that, if cancelled, indicates the lock was not taken, /// and must be awaited to obtain the token that will release the /// lock on <see cref="IDisposable.Dispose"/>. /// </returns> /// <exception cref="TaskCanceledException">The <paramref name="timeout"/> expires before the lock could be taken.</exception> /// <remarks> /// <para> /// The following illustrates an example of using try-catch to detect a failure to take the lock. /// </para> /// /// <code> /// AsyncLock asyncLock = new AsyncLock(); /// /// try /// { /// using IDisposable token = await asyncLock.TryEnterAsync(); /// // Critical region /// } /// catch (TaskCanceledException) /// { /// // Lock failed /// } /// </code> /// /// <para> /// The following illustrates an example of using <see cref="Task.ContinueWith{TResult}(Func{Task, TResult})"/> /// to detect a failure to take the lock. /// </para> /// /// <code> /// AsyncLock asyncLock = new AsyncLock(); /// /// await asyncLock.TryEnterAsync().ContinueWith(async tokenTask => /// { /// if (tokenTask.IsCanceled) /// { /// // Lock failed /// return; /// } /// /// using IDisposable token = await tokenTask; /// // Critical region /// }).Unwrap(); /// </code> /// </remarks> public async Task <IDisposable> TryEnterAsync(TimeSpan timeout) { LockEntry lockEntry = new LockEntry(); try { Task timeoutTask = Task.Delay(timeout); Task lockReleasedTask = Interlocked.Exchange(ref m_task, lockEntry.WhenReleased()); await Task.WhenAny(timeoutTask, lockReleasedTask).ConfigureAwait(false); if (!lockReleasedTask.IsCompleted) { throw new TaskCanceledException("Timed out while attempting to get exclusive access to async lock."); } return(lockEntry); } catch { // Make sure to dispose the lock entry since // it's not being returned to the caller lockEntry.Dispose(); throw; } }
/// <summary> /// Attempts to enter the lock with concurrent access where all /// readers can execute concurrently with respect to each other. /// </summary> /// <param name="timeout">The amount of time to wait before timing out.</param> /// <returns>The token used to control the duration of entry.</returns> /// <exception cref="TimeoutException">The lock could not be entered before the <paramref name="timeout"/> expired.</exception> public async Task <IDisposable> TryEnterReadLockAsync(TimeSpan timeout) { LockEntry lockEntry = new LockEntry(); try { Task readerTask = lockEntry.WhenReleased(); // The exclusive lock must be obtained to update the reader list, // but it's also necessary so that the reader will wait on any // writers that entered before it using (await ReaderListLock.TryEnterAsync(timeout).ConfigureAwait(false)) ReaderList.Add(readerTask); // Prevent the reader list from growing indefinitely _ = readerTask.ContinueWith(async _ => { using (await ReaderListLock.EnterAsync().ConfigureAwait(false)) ReaderList.Remove(readerTask); }); return(lockEntry); } catch { // Make sure to dispose the lock entry since // it's not being returned to the caller lockEntry.Dispose(); throw; } }
/// <summary> /// Attempts to enter the lock with exclusive access where no other /// readers or writers can execute concurrently with the writer. /// </summary> /// <param name="timeout">The amount of time to wait before timing out.</param> /// <returns>The token used to control the duration of entry.</returns> /// <exception cref="TimeoutException">The lock could not be entered before the <paramref name="timeout"/> expired.</exception> public async Task <IDisposable> TryEnterWriteLockAsync(TimeSpan timeout) { LockEntry lockEntry = new LockEntry(); try { // The writer must maintain exclusive access until the write operation is complete IDisposable readerListToken = await ReaderListLock.TryEnterAsync(timeout).ConfigureAwait(false); _ = lockEntry.WhenReleased().ContinueWith(_ => readerListToken.Dispose()); Task timeoutTask = Task.Delay(timeout); Task readerTask = Task.WhenAll(ReaderList); await Task.WhenAny(timeoutTask, readerTask).ConfigureAwait(false); if (!readerTask.IsCompleted) { throw new TaskCanceledException("Timed out waiting for readers to complete."); } // Completed readers will eventually remove themselves, // but may as well remove them all here ReaderList.Clear(); return(lockEntry); } catch { // Make sure to dispose the lock entry since // it's not being returned to the caller lockEntry.Dispose(); throw; } }
/// <summary> /// Enters the lock with exclusive access where no other /// readers or writers can execute concurrently with the writer. /// </summary> /// <returns>The token used to control the duration of entry.</returns> public async Task <IDisposable> EnterWriteAsync() { LockEntry lockEntry = new LockEntry(); // The writer must maintain exclusive access until the write operation is complete IDisposable readerListToken = await ReaderListLock.EnterAsync().ConfigureAwait(false); _ = lockEntry.WhenReleased().ContinueWith(_ => readerListToken.Dispose()); await Task.WhenAll(ReaderList).ConfigureAwait(false); ReaderList.Clear(); return(lockEntry); }
/// <summary> /// Enters the lock with concurrent access where all readers /// can execute concurrently with respect to each other. /// </summary> /// <returns>The token used to control the duration of entry.</returns> public async Task <IDisposable> EnterReadAsync() { LockEntry lockEntry = new LockEntry(); Task readerTask = lockEntry.WhenReleased(); // The exclusive lock must be obtained to update the reader list, // but it's also necessary so that the reader will wait on any // writers that entered before it using (await ReaderListLock.EnterAsync().ConfigureAwait(false)) ReaderList.Add(readerTask); // Prevent the reader list from growing indefinitely _ = readerTask.ContinueWith(async _ => { using (await ReaderListLock.EnterAsync().ConfigureAwait(false)) ReaderList.Remove(readerTask); }); return(lockEntry); }