/// <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; } }