This project has moved. For the latest updates, please go here.
1

Closed

Asynchronous Lock Primitives suggestion

description

I would like to suggest a change to the Asynchronous Lock Primitives that should not break the public API (at least the way it should be used) and could help prevent incorrect usage at compile time.

Lets say I have some code that wants to use the AsyncLock primitive to coordinate the access to a shared resource. The code will look something like this:
public async Task SomthingAsync()
{
    using (await this.lock.LockAsync())
    {
        // TODO: Access a shared resource
        await Task.Delay(100);
    }
}
However, the following code will also compile without error (but seriously breaks the lock pattern; notice the missing await in the "using" line)
public async Task SomthingAsync()
{
    using (this.lock.LockAsync())
    {
        // TODO: Access a shared resource
        await Task.Delay(100);
    }
}
The reason the last piece of code compiles, is because the compiler expects an object that implements IDisposable to be passed to the "using" and since "LockAsync()" returns a Task<IDisposable> there is no problem to the compiler, since System.Threading.Tasks.Task<T> implements IDisposable it self. But in fact what the code expects is the IDisposable result of the returned task to be passed to the using (not the task it self).

So my suggestion is that you change the AsyncLock.LockAsync() method (and any other async lock methods of other Asynchronous Lock Primitives) to return a TaskAwaiter<IDisposable> structure (or any other custom awaitable structure that does not implement IDisposable). With this change the the second example (above) will not compile and then prevent the incorrect use of the Asynchronous Lock Primitives, but the first example will still compile (as expected).
Closed Sep 5, 2014 at 2:58 AM by StephenCleary
Fixed in v3.0.0 (on GitHub).

comments

StephenCleary wrote Nov 6, 2013 at 9:17 PM

This would probably work best as a custom (non-disposable) awaitable type that also provides an "AsTask()" method for the rarer use cases.