Async Reader/Writer Lock
AsyncReaderWriterLock is a non-blocking asynchronous alternative to ReaderWriteLockSlim with the same semantics.
This class supports methods to determine whether locks are held or contended. These methods are designed for monitoring system state, not for synchronization control.
The reader lock and writer lock both support interruption during lock acquisition using CancellationToken.
using System.Threading;
using DotNext.Threading;
var rwlock = new AsyncReaderWriterLock();
await rwlock.EnterReadLockAsync(CancellationToken.None);
try
{
//reader stuff here
}
finally
{
rwlock.Release();
}
await rwlock.EnterWriteLockAsync(TimeSpan.FromSecond(2));
try
{
//writer stuff here
}
finally
{
rwlock.Release();
}
await rwlock.UpgradeToWriteLockAsync(TimeSpan.FromSecond(2), CancellationToken.None);
try
{
//writer stuff here
}
finally
{
rwlock.DowngradeFromWriteLock();
}
//or with 'using statement'
using static DotNext.Threading.AsyncLockAcquisition;
using (await AcquireReadLockAsync(rwlock, CancellationToken.None))
{
//reader stuff here
}
using (await AcquireWriteLockAsync(rwlock, TimeSpan.FromSecond(2)))
{
//writer stuff here
}
Exclusive lock should be destroyed if no longer needed by calling Dispose method which is not thread-safe.
Behavior of UpgradeToWriteLockAsync and DowngradeFromWriteLock methods are the same as in ReaderWriterLock:
UpgradeToWriteLockAsyncreleases the reader lock and tries to acquire the writer lock. If writer lock is not available for acquisition, the caller goes to the end of the wait queueUpgradeToWriteLockAsyncandTryUpgradeToWriteLockalways release the reader lock even if the writer lock cannot be acquired immediately, or if the method throws
You need to be in the reader lock to call the upgrade. After calling of DowngradeFromWriteLock method the current flow keeping the reader lock so you need to call Release method to release the lock completely
Acquisition Order
This lock does not impose a reader or writer preference ordering for lock access. However, it respects fairness policy. It means that callers contend for entry using an approximately arrival-order policy. When the currently held lock is released either the longest-waiting single writer will be assigned the writer lock, or if there is a group of readers waiting longer than all waiting writers, that group will be assigned the reader lock.
A caller that tries to acquire a reader lock (non-reentrant) will enqueued if either the writer lock is held, or there is a waiting writer. The caller will not acquire the reader lock until after the oldest currently waiting writer has acquired and released the writer lock. Of course, if a waiting writer abandons its wait, leaving one or more readers as the longest waiters in the queue with the writer lock free, then those readers will be assigned the reader lock.
A caller that tries to acquire a writer lock (non-reentrant) will block unless both the reader lock and writer lock are free (which implies there are no waiters).
Graceful Shutdown
Dispose method is not thread-safe and may cause unpredictable behavior if called on the lock which was acquired previously. This is happening because the method doesn't wait for the lock to be released. Starting with version 2.6.0 this type of lock implements IAsyncDisposable interface and provides a way for graceful shutdown. DisposeAsync behaves in the following way:
- If lock is not acquired then completes synchronously
- If lock is acquired then suspends the caller and wait when it will be released, then dispose the lock
Synchronous acquisition
The class exposes TryEnterReadLock(TimeSpan) and TryEnterWriteLock(TimeSpan) blocking methods that can be used by synchronous callers. The method allows to perform mixed synchronization for synchronous and asynchronous code at the same time.