Table of Contents

AsyncReaderWriterLock Class

Definition

Namespace
Bodu.Threading
Assembly
Bodu.Core.dll
Package
Bodu.Core 1.0.1
Source
AsyncReaderWriterLock.Releaser.cs

Provides an asynchronous, writer-preferring reader/writer lock whose acquisitions can be awaited without blocking a thread.

public sealed class AsyncReaderWriterLock : IDisposable
Inheritance
AsyncReaderWriterLock
Implements
Inherited Members
Extension Methods

Examples

private readonly AsyncReaderWriterLock _lock = new();

// Readers run concurrently with one another.
async Task<int> CountAsync()
{
    using (await _lock.ReaderAsync())
        return _cache.Count;
}

// A writer runs exclusively, excluding all readers for its duration.
async Task SetAsync(string key, int value)
{
    using (await _lock.WriterAsync())
        _cache[key] = value;
}

Remarks

AsyncReaderWriterLock permits any number of concurrent readers or a single exclusive writer. Read access is acquired with ReaderAsync() and write access with WriterAsync(); both return a AsyncReaderWriterLock.Releaser whose disposal releases the corresponding access, typically scoped with a using statement.

Fairness. The lock is writer-preferring: while a writer is active or queued, newly arriving readers wait, and queued writers are granted in strict FIFO order. When a writer releases and no writer is queued, all waiting readers are admitted together (batch admission). This prevents writer starvation, but a sustained stream of writers can starve readers; choose this lock only when that trade-off is acceptable.

Reentrancy and upgrade. The lock is not reentrant and provides no upgradeable read mode. A caller must never acquire write access while already holding read access (or vice versa) on the same flow: doing so deadlocks, because the second acquisition waits for access the same flow already holds. To transition from reading to writing, release the read access first, then acquire the write access and re-validate state, accepting that the gap is not atomic.

Cancellation. Following the package-wide rule, access that can be granted immediately is granted even when the supplied token is already canceled; the token only cancels an acquisition that must queue, and cancellation removes only the calling waiter.

Continuations and disposal. All waiter completion sources use RunContinuationsAsynchronously, so releasing threads never run waiter continuations inline while holding the internal gate. Disposing the lock faults any still-waiting acquisition with ObjectDisposedException; dispose only when no acquisitions are expected to be granted afterwards.

Releaser ownership. Each AsyncReaderWriterLock.Releaser is idempotent: disposing it more than once (including copies of the same value) releases the access exactly once, so accidental double-disposal cannot corrupt the reader count or grant overlapping access.

Constructors

AsyncReaderWriterLock()

Initializes a new instance of the AsyncReaderWriterLock class in the released state.

public AsyncReaderWriterLock()

Methods

Dispose()

Releases the resources used by the lock. Any callers still waiting observe an ObjectDisposedException.

public void Dispose()

ReaderAsync()

Asynchronously acquires shared (read) access.

public ValueTask<AsyncReaderWriterLock.Releaser> ReaderAsync()

Returns

ValueTask<AsyncReaderWriterLock.Releaser>

A ValueTask<TResult> yielding a AsyncReaderWriterLock.Releaser whose disposal releases read access.

Exceptions

ObjectDisposedException

The lock has been disposed.

ReaderAsync(CancellationToken)

Asynchronously acquires shared (read) access, observing a cancellation request while waiting.

public ValueTask<AsyncReaderWriterLock.Releaser> ReaderAsync(CancellationToken cancellationToken)

Parameters

cancellationToken CancellationToken

A token used to cancel the pending acquisition.

Returns

ValueTask<AsyncReaderWriterLock.Releaser>

A ValueTask<TResult> yielding a AsyncReaderWriterLock.Releaser whose disposal releases read access.

Remarks

The returned ValueTask<TResult> must be awaited exactly once. Read access that can be granted immediately is granted even when cancellationToken is already canceled; the token only cancels an acquisition that must queue.

Exceptions

ObjectDisposedException

The lock has been disposed.

OperationCanceledException

cancellationToken was canceled before access was acquired.

WriterAsync()

Asynchronously acquires exclusive (write) access.

public ValueTask<AsyncReaderWriterLock.Releaser> WriterAsync()

Returns

ValueTask<AsyncReaderWriterLock.Releaser>

A ValueTask<TResult> yielding a AsyncReaderWriterLock.Releaser whose disposal releases write access.

Exceptions

ObjectDisposedException

The lock has been disposed.

WriterAsync(CancellationToken)

Asynchronously acquires exclusive (write) access, observing a cancellation request while waiting.

public ValueTask<AsyncReaderWriterLock.Releaser> WriterAsync(CancellationToken cancellationToken)

Parameters

cancellationToken CancellationToken

A token used to cancel the pending acquisition.

Returns

ValueTask<AsyncReaderWriterLock.Releaser>

A ValueTask<TResult> yielding a AsyncReaderWriterLock.Releaser whose disposal releases write access.

Remarks

The returned ValueTask<TResult> must be awaited exactly once. Write access that can be granted immediately is granted even when cancellationToken is already canceled; the token only cancels an acquisition that must queue.

Exceptions

ObjectDisposedException

The lock has been disposed.

OperationCanceledException

cancellationToken was canceled before access was acquired.

Applies to

ProductVersions
.NET8, 10