Skip to content

Threading ▪ Locks ▪ Async Lock

Squared Infinity edited this page May 22, 2017 · 2 revisions

AsyncLock

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.
    }
}

Non Recursive AsyncLock

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())
    {
        // ..
    }
}

Read Locks

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.

Clone this wiki locally