The await keyword in C# (.NET Async CTP) is not allowed from within a lock statement.
From MSDN:
An await expression cannot be used in a synchronous function, in a query expression, in the catch or finally block of an exception handling statement, in the block of a lock statement, or in an unsafe context.
I assume this is either difficult or impossible for the compiler team to implement for some reason.
I attempted a work around with the using statement:
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(); }
However this does not work as expected. The call to Monitor.Exit within ExitDisposable.Dispose seems to block indefinitely (most of the time) causing deadlocks as other threads attempt to acquire the lock. I suspect the unreliability of my work around and the reason await statements are not allowed in lock statement are somehow related.
Does anyone know why await isn't allowed within the body of a lock statement?
Technically, yes, but it won't work as you expect. There are two reasons why thread-affine locks don't play well with async . One is that (in the general case), an async method may not resume on the same thread, so it would try to release a lock it doesn't own while the other thread holds the lock forever.
The await operator is used to wait for a Promise . It can only be used inside an async function within regular JavaScript code; however it can be used on its own with JavaScript modules.
The await keyword, by contrast, is non-blocking, which means the current thread is free to do other things during the wait.
The lock statement was introduced in c# 2.0 to prevent multi threads access to an object at the same time. In async programming model the goal of the locks is to limit the number of concurrent execution of a block of code to a defined number.
I assume this is either difficult or impossible for the compiler team to implement for some reason.
No, it is not at all difficult or impossible to implement -- the fact that you implemented it yourself is a testament to that fact. Rather, it is an incredibly bad idea and so we don't allow it, so as to protect you from making this mistake.
call to Monitor.Exit within ExitDisposable.Dispose seems to block indefinitely (most of the time) causing deadlocks as other threads attempt to acquire the lock. I suspect the unreliability of my work around and the reason await statements are not allowed in lock statement are somehow related.
Correct, you have discovered why we made it illegal. Awaiting inside a lock is a recipe for producing deadlocks.
I'm sure you can see why: arbitrary code runs between the time the await returns control to the caller and the method resumes. That arbitrary code could be taking out locks that produce lock ordering inversions, and therefore deadlocks.
Worse, the code could resume on another thread (in advanced scenarios; normally you pick up again on the thread that did the await, but not necessarily) in which case the unlock would be unlocking a lock on a different thread than the thread that took out the lock. Is that a good idea? No.
I note that it is also a "worst practice" to do a yield return
inside a lock
, for the same reason. It is legal to do so, but I wish we had made it illegal. We're not going to make the same mistake for "await".
Use SemaphoreSlim.WaitAsync
method.
await mySemaphoreSlim.WaitAsync(); try { await Stuff(); } finally { mySemaphoreSlim.Release(); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With