AsyncReaderWriterLockSlim 1.0.0

dotnet add package AsyncReaderWriterLockSlim --version 1.0.0
NuGet\Install-Package AsyncReaderWriterLockSlim -Version 1.0.0
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AsyncReaderWriterLockSlim" Version="1.0.0" />
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add AsyncReaderWriterLockSlim --version 1.0.0
#r "nuget: AsyncReaderWriterLockSlim, 1.0.0"
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install AsyncReaderWriterLockSlim as a Cake Addin
#addin nuget:?package=AsyncReaderWriterLockSlim&version=1.0.0

// Install AsyncReaderWriterLockSlim as a Cake Tool
#tool nuget:?package=AsyncReaderWriterLockSlim&version=1.0.0

AsyncReaderWriterLockSlim

This is an alternative to .NET's ReaderWriterLockSlim with a similar functionality, but can be used in async methods. Due to its async-readiness, it does not support recursive locks (see section Differences).

Another alternative: https://dotnet.github.io/dotNext/api/DotNext.Threading.AsyncReaderWriterLock.html from https://github.com/dotnet/dotNext

Lock Modes

The lock can have different modes:

  • Read mode: One or more read mode locks can be active at a time while no write mode lock is active.
  • Write mode: One write mode lock can be active at a time while no other write mode locks and no other read mode locks are active.

When a task or thread ("execution flow") tries to enter a write mode lock while at least one read mode lock is active, it is blocked until the last read mode lock is released.

When a task or thread tries to enter a read mode lock while a write mode lock is active, it is blocked until the write mode lock is released.

If, while other read mode locks are active and the current task or thread waits to enter the write mode lock, another task or thread tries to enter a read mode lock, it is blocked until the current task or thread released the write mode lock (or canceled the wait operation), which means writers are favored in this case.

Also, when a write mode lock is released while there are one or more execution flows trying to enter a write mode lock and also one or more execution flows trying to enter a read mode lock, writers are favored.

Lock Methods

The lock provides synchronous Enter...() methods for the different lock modes that block until the lock has been acquired, and asynchronous Enter...Async() methods that "block asynchronously" by returning a Task that will complete once the lock has been acquired.

For each Enter...() and Enter...Async() method there is also a TryEnter...() and TryEnter...Async() method that allow you to specify an integer time-out, and return a Boolean that indicates if the lock could be acquired within that time-out.

You must make sure to call the corresponding Exit...() method to release the lock once you don't need it anymore.

Additionally, the AsyncReaderWriterLockSlimExtension class contains extension methods that return an IDisposable so that the lock can be used with a using block.

Performance Considerations

When using the AsyncReaderWriterLockSlim for write mode locks only, performance is significantly slower than simply using a SemaphoreSlim due to the additional overhead. Therefore, use the AsyncReaderWriterLockSlim only when there are far more readers than writers.

Differences to ReaderWriterLockSlim

This implementation has the following differences to .NET's ReaderWriterLockSlim:

  • The lock is not thread-affine, which means one thread can enter the lock, and a different thread can release it. This allows you to use the lock in an async method with a await operator between entering and releasing the lock.
  • Additionally to synchronous methods like EnterReadLock, it has asynchronous methods like EnterReadLockAsync which can be called in async methods, so that the current thread is not blocked while waiting for the lock.
  • You can specify a CancellationToken when entering a lock to cancel the wait operation.
  • Because this lock is not thread-affine, recursive locks are not supported (which also means they cannot be detected). In order for the lock to work correctly, you must not recursively enter the lock from the same execution flow.
  • The lock does not support upgradeable read mode locks that can be upgraded to a write mode lock, due to the complexity this would add.

Differences to Nito.AsyncEx.AsyncReaderWriterLock

This implementation has the following differences to Nito.AsyncEx' AsyncReaderWriterLock:

  • Instead of methods that return an IDisposable, it has Enter...() and Exit...() methods similar to .NET's ReaderWriterLockSlim. However, the class AsyncReaderWriterLockSlimExtension provides extension methods that return an IDisposable.
  • Additionally to providing a CancellationToken that allows you to cancel the wait operation, you can supply an integer time-out to the TryEnter...() methods.
  • When calling one of the Enter...() methods with an already canceled CancellationToken, the method does not try to acquire the lock, but instead throws a OperationCanceledException, which matches the behavior of SemaphoreSlim. <br> To try to acquire the lock without blocking, you can call one of the Try... methods without specifying a timeout (or specify a timeout of 0).
  • You can downgrade a write mode lock to a read mode lock by calling DowngradeWriteLockToReadLock().
  • Non-async methods do not require a ThreadPool thread to run the unblock logic; instead all code is executed in the thread that called the synchronous method.<br> This means e.g. synchronous methods can still work even when the ThreadPool (used for async task continuations) is currently exhausted, but only if you didn't call async methods at the same time that are still waiting to get the lock.
  • The lock is not fair (just as the underlying SemaphoreSlim), which means there is no guarantee in which order threads will acquire the lock (e.g. if multiple threads want to get a write lock at the same time using synchronous methods).<br> Fairness can actually lead to problems like lock convoys, and shouldn't be needed in most cases.

Additional Infos

Note: The supported maximum number of concurrent locks in read mode is limited to int.MaxValue (2147483647).

The lock internally uses SemaphoreSlim to implement wait functionality.

API Surface

Method Description
Dispose () Releases all resources used by the AsyncReaderWriterLockSlim.
EnterReadLock (CancellationToken) Enters the lock in read mode.
EnterReadLockAsync (CancellationToken) Asynchronously enters the lock in read mode.
TryEnterReadLock (Int32, CancellationToken) Tries to enter the lock in read mode, with an optional integer time-out.
TryEnterReadLockAsync (Int32, CancellationToken) Tries to asynchronously enter the lock in read mode, with an optional integer time-out.
EnterWriteLock (CancellationToken) Enters the lock in write mode.
EnterWriteLockAsync (CancellationToken) Asynchronously enters the lock in write mode.
TryEnterWriteLock (Int32, CancellationToken) Tries to enter the lock in write mode, with an optional integer time-out.
TryEnterWriteLockAsync (Int32, CancellationToken) Tries to asynchronously enter the lock in write mode, with an optional integer time-out.
DowngradeWriteLockToReadLock () Downgrades the lock from write mode to read mode.
ExitReadLock () Exits read mode.
ExitWriteLock () Exits write mode.

Examples

Enter the lock in read mode within an async method:

private async Task TestReadModeAsync(AsyncReaderWriterLockSlim asyncLock)
{
    // Asynchronously enter the lock in read mode. The task completes after the lock
    // has been acquired.
    await asyncLock.EnterReadLockAsync();
    try
    {
        // Use Task.Delay to simulate asynchronous work, which means a different thread
        // might continue execution after this point.
        await Task.Delay(200);
    }
    finally
    {
        asyncLock.ExitReadLock();
    }
}

Enter the lock in read mode within an async method within an using block:

private async Task TestReadModeAsync(AsyncReaderWriterLockSlim asyncLock)
{
    // Asynchronously enter the lock in read mode. The task completes after the lock
    // has been acquired.
    // As the Get...() methods return a IDisposeable, you can use the lock within an
    // "using" block.
    using (var myLock = await asyncLock.GetReadLockAsync())
    {
        // Use Task.Delay to simulate asynchronous work, which means a different thread
        // might continue execution after this point.
        await Task.Delay(200);
    }
}

Enter the lock in write mode in a synchronous method, using a timeout:

private void TestWriteMode(AsyncReaderWriterLockSlim asyncLock)
{
    // Try to enter the lock within 2 seconds.
    if (asyncLock.TryEnterWriteLock(2000))
    {
        try
        {
            // Simulate some work...
            Thread.Sleep(200);
        }
        finally
        {
            asyncLock.ExitWriteLock();
        }
    }
    else
    {
        // We could not enter the lock within the timeout...
    }
}
Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.0 306 4/4/2024