-
Notifications
You must be signed in to change notification settings - Fork 0
Threading ▪ Locks ▪ Async Lock
AsyncLock provides an exclusive access to critical section of code. Only one thread can acquire AsyncLock at a time:
readonly IAsyncLock Lock = new AsyncLock();
public async Task Example()
{
// acquire lock asynchronously
using(await Lock.AcquireWriteLockAsync())
{
// no other thread will have concurrent access to this critical section
// it is safe to use async code inside critical section
await Task.Delay(500);
// but be carefult, you may now be on a different thread than thread which acquired the lock in a first place.
}
}
Default behavior AsyncLock is to be non recursive, this means that following code will cause LockRecursionException to be thrown on attempt to acquire alread held lock:
readonly IAsyncLock Lock = new AsyncLock();
public async Task Example()
{
// acquire lock asynchronously
using(await Lock.AcquireWriteLockAsync())
{
// this will throw LockRecursionException
using(await Lock.AcquireWriteLockAsync())
{
// ..
}
}
}
It is easy to avoid writing code as above, but quite often second attemt to acquire a lock will be hidden somewhere in code tree:
readonly IAsyncLock Lock = new AsyncLock();
public async Task Example()
{
// acquire lock asynchronously
using(await Lock.AcquireWriteLockAsync())
{
DoStomething();
}
}
public void DoSomething()
{
using(Lock.AcquireWriteLock())
{
// ..
}
}
One way to avoid such scenarios is to have an internal, non-blocking version of methods:
readonly IAsyncLock Lock = new AsyncLock();
public async Task Example()
{
// acquire lock asynchronously
using(await Lock.AcquireWriteLockAsync())
{
DoStomething_NOLOCK();
}
}
public void DoSomething()
{
using(Lock.AcquireWriteLock())
{
DoSomething_NOLOCK();
}
}
// this non-blocking version of .DoSomething() will only ever be called internally
void DoSomething_NOLOCK()
{
// ..
}
Another options is to allow lock recursion:
readonly IAsyncLock Lock = new AsyncLock(recursionPolicy: LockRecursionPolicy.SupportsRecursion);
public async Task Example()
{
// acquire lock asynchronously
using(await Lock.AcquireWriteLockAsync())
{
// it is now safe to call DoSomething() and try to re-acquire same block
DoStomething();
}
}
public void DoSomething()
{
using(Lock.AcquireWriteLock())
{
// ..
}
}
AsyncLock exposes IReadLock interface to allow read locks to be acquired. This interface is for convinience only and allow AsyncLock to be used in scenarios where read/write lock would be used instead. Internally AsyncLock allows only singe read or single write happening at a same time. There is no priority given to either reads or writes.