Problem
In C# (.NET Async CTP), the await keyword isn’t allowed inside a lock statement.
From MSDN:
For some reason, I presume this is difficult or impossible for the compiler team to accomplish.
With the using statement, I attempted a workaround:
class Async
{
public static async Task<IDisposable> Lock(object obj)
{
while (!Monitor.TryEnter(obj))
await TaskEx.Yield();
return new ExitDisposable(obj);
}
private class ExitDisposable : IDisposable
{
private readonly object obj;
public ExitDisposable(object obj) { this.obj = obj; }
public void Dispose() { Monitor.Exit(this.obj); }
}
}
// example usage
using (await Async.Lock(padlock))
{
await SomethingAsync();
}
This, however, does not work as planned. Monitor has been contacted. ExitDisposable within ExitDisposable Dispose appears to block endlessly (most of the time), resulting in deadlocks while other threads try to get the lock. I believe my workaround’s unreliability and the fact that await statements aren’t allowed in lock statements are linked in some way.
Is there a reason why await isn’t allowed inside a lock statement?
Asked by Kevin
Solution #1
No, it’s not difficult or impossible to accomplish; the fact that you did it yourself is proof of that. Instead, it’s a terrible idea, and we won’t let you do it to keep you from making the same error.
Yes, you’ve figured out why we made it illegal. A recipe for deadlocks can be found waiting inside a lock.
I’m sure you can see why: during the time the await returns control to the caller and the method restarts, arbitrary code runs. That arbitrary code could be removing locks, causing lock ordering inversions and, as a result, deadlocks.
Worse, the function could continue on a different thread (in advanced cases; generally, you pick up on the thread that did the await, but this isn’t always the case), in which case the unlock would be unlocking a lock on a thread other than the one that pulled it out. Is that a good suggestion? No.
For the same reason, doing a yield return inside a lock is considered “bad practice.” Although it is legal, I wish we had made it unlawful. For “await,” we’re not going to make the same mistake.
Answered by Eric Lippert
Solution #2
Use SemaphoreSlim.WaitAsync method.
await mySemaphoreSlim.WaitAsync();
try {
await Stuff();
} finally {
mySemaphoreSlim.Release();
}
Answered by user1639030
Solution #3
It would essentially be the incorrect thing to do.
There are two options for implementing this:
So there are two competing criteria here: you shouldn’t try to perform the first here, and if you want to do the second, you can make the code much clearer by having two independent lock blocks separated by the await expression:
// Now it's clear where the locks will be acquired and released
lock (foo)
{
}
var result = await something;
lock (foo)
{
}
As a result, by banning you from awaiting in the lock block itself, the language forces you to consider what you truly want to do, and makes that decision apparent in your code.
Answered by Jon Skeet
Solution #4
This is merely a continuation of the previous response.
using System;
using System.Threading;
using System.Threading.Tasks;
public class SemaphoreLocker
{
private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
public async Task LockAsync(Func<Task> worker)
{
await _semaphore.WaitAsync();
try
{
await worker();
}
finally
{
_semaphore.Release();
}
}
// overloading variant for non-void methods with return type (generic T)
public async Task<T> LockAsync<T>(Func<Task<T>> worker)
{
await _semaphore.WaitAsync();
try
{
return await worker();
}
finally
{
_semaphore.Release();
}
}
}
Usage:
public class Test
{
private static readonly SemaphoreLocker _locker = new SemaphoreLocker();
public async Task DoTest()
{
await _locker.LockAsync(async () =>
{
// [async] calls can be used within this block
// to handle a resource by one thread.
});
// OR
var result = await _locker.LockAsync(async () =>
{
// [async] calls can be used within this block
// to handle a resource by one thread.
});
}
}
Answered by Sergey
Solution #5
Building Async Coordination Primitives, Part 6: AsyncLock, http://winrtstoragehelper.codeplex.com/, Windows 8 app store, and.net 4.5 are all mentioned.
Here’s how I see things:
The async/await language feature simplifies a lot of things, but it also presents a circumstance that was rarely encountered before async calls were so simple to use: reentrance.
This is especially true for event handlers, because after you return from an event handler, you often have no idea what’s going on. One possibility is that the async method you’re waiting for in the first event handler is called from a different event handler on the same thread.
Here’s an example of a real-life scenario I saw in a Windows 8 App Store app: My program has two frames: one entering and one exiting. I’d like to load/save some data to a file/storage. For saving and loading, the OnNavigatedTo/From events are used. Some async utility function (such as http://winrtstoragehelper.codeplex.com/) handles the saving and loading. The async load and safe operations are called and awaited while going from frame 1 to frame 2 or the other way around. They can’t be awaited since the event handlers become async and return void.
However, because the utility’s first file open action (let’s say, inside a save function) is also async, the first await returns control to the framework, which then runs the other utility (load) via the second event handler. The load now tries to open the same file and fails with an ACCESSDENIED exception if the file is already open for the save operation.
For me, the bare minimum answer is to use a using and an AsyncLock to secure file access.
private static readonly AsyncLock m_lock = new AsyncLock();
...
using (await m_lock.LockAsync())
{
file = await folder.GetFileAsync(fileName);
IRandomAccessStream readStream = await file.OpenAsync(FileAccessMode.Read);
using (Stream inStream = Task.Run(() => readStream.AsStreamForRead()).Result)
{
return (T)serializer.Deserialize(inStream);
}
}
Please notice that his lock effectively disables all file operations for the utility with a single lock, which is overly powerful yet adequate for my needs.
Here’s my test project: a Windows 8 app store app with some test calls for both the original version (http://winrtstoragehelper.codeplex.com/) and my modified version (which uses Stephen Toub’s AsyncLock).
Please see http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx for more information.
Answered by hans
Post is based on https://stackoverflow.com/questions/7612602/why-cant-i-use-the-await-operator-within-the-body-of-a-lock-statement