为什么我不能使用“await”锁定语句体内的运算符?
lock
语句中不允许使用 C# (.NET Async CTP) 中的 await
关键字。
来自 MSDN:
安 wait 表达式不能在同步函数、查询中使用 表达式,在异常处理的 catch 或 finally 块中 语句,在锁定语句的块中,或在不安全的上下文中。
我认为由于某种原因,编译器团队很难或不可能实现这一点。
我尝试使用 using 语句解决问题:
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();
}
但这并没有按预期工作。在 ExitDisposable.Dispose
中对 Monitor.Exit
的调用似乎无限期地阻塞(大多数时候),导致当其他线程尝试获取锁时出现死锁。我怀疑我的工作不可靠,并且 lock
语句中不允许 await
语句的原因在某种程度上是相关的。
有谁知道为什么 await
不允许在 lock
语句体内使用?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
不,实施起来一点也不困难或不可能——您自己实施的事实就证明了这一事实。相反,这是一个非常糟糕的主意,因此我们不允许这样做,以防止您犯此错误。
是的,您已经发现为什么我们将其定为非法。 在锁内等待是产生死锁的一个因素。
我相信您可以明白原因:在等待将控制权返回给调用者和方法恢复之间运行任意代码 >。该任意代码可能会取出产生锁顺序反转的锁,从而导致死锁。
更糟糕的是,代码可能会在另一个线程上恢复(在高级场景中;通常您会在执行等待的线程上再次拿起,但不一定),在这种情况下,解锁将解锁某个线程上的锁与取出锁的线程不同的线程。这是个好主意吗?不
。我注意到,出于同样的原因,在
lock
内执行yield return
也是“最糟糕的做法”。这样做是合法的,但我希望我们将其定为非法。我们不会在“await”上犯同样的错误。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.
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 alock
, 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".使用
SemaphoreSlim.WaitAsync
< /a> 方法。Use the
SemaphoreSlim.WaitAsync
method.这只是 此答案,作者:1639030。
基本版本
用法:
扩展版本
声称完全死锁安全的
LockAsync
方法版本(来自Jez 建议的第四次修订)。用法:
This is just an extension to this answer by user1639030.
Basic Version
Usage:
Extended Version
A version of the
LockAsync
method that claims to be completely deadlock-safe (from the 4th revision suggested by Jez).Usage:
基本上这是错误的做法。
有两种方法可以实现此目的:
保持锁定,仅在块末尾释放它。
这是一个非常糟糕的主意,因为您不知道异步操作将花费多长时间。您应该只持有锁最短时间。这也可能是不可能的,因为线程拥有锁,而不是方法 - 并且您甚至可能无法在同一线程上执行异步方法的其余部分(取决于任务调度程序)。
释放await中的锁,并在await返回时重新获取锁
这违反了 IMO 的最小惊讶原则,其中异步方法的行为应尽可能类似于等效的同步代码 - 除非您在锁块中使用
Monitor.Wait
,否则您希望拥有锁块的持续时间。因此,基本上这里有两个相互竞争的要求 - 您不应该尝试在这里执行第一个要求,如果您想采用第二种方法,您可以通过使用两个单独的锁块来使代码更加清晰由await表达式分隔:
因此,通过禁止您在锁块本身中等待,该语言迫使您思考您真正想要做什么,并在您希望的代码中使该选择更加清晰写。
Basically it would be the wrong thing to do.
There are two ways this could be implemented:
Keep hold of the lock, only releasing it at the end of the block.
This is a really bad idea as you don't know how long the asynchronous operation is going to take. You should only hold locks for minimal amounts of time. It's also potentially impossible, as a thread owns a lock, not a method - and you may not even execute the rest of the asynchronous method on the same thread (depending on the task scheduler).
Release the lock in the await, and reacquire it when the await returns
This violates the principle of least astonishment IMO, where the asynchronous method should behave as closely as possible like the equivalent synchronous code - unless you use
Monitor.Wait
in a lock block, you expect to own the lock for the duration of the block.So basically there are two competing requirements here - you shouldn't be trying to do the first here, and if you want to take the second approach you can make the code much clearer by having two separated lock blocks separated by the await expression:
So by prohibiting you from awaiting in the lock block itself, the language is forcing you to think about what you really want to do, and making that choice clearer in the code that you write.
这是指构建异步协调基元,第 6 部分: AsyncLock , http://winrtstoragehelper.codeplex.com/ 、Windows 8 应用商店和 .net 4.5
以下是我对此的看法:
async/await 语言功能使许多事情变得相当简单,但它也引入了一个场景那是
以前很少遇到这么容易使用异步调用:重入。
对于事件处理程序来说尤其如此,因为对于许多事件,您对从事件处理程序返回后发生的情况没有任何线索。
实际可能发生的一件事是,您在第一个事件处理程序中等待的异步方法会从仍在该事件处理程序上的另一个事件处理程序中调用。
同一个线程。
这是我在 Windows 8 应用商店应用程序中遇到的一个真实场景:
我的应用程序有两个框架:进入和离开一个框架,我想将一些数据加载/安全到文件/存储。
OnNavigedTo/From 事件用于保存和加载。保存和加载是通过一些异步实用函数完成的(例如 http://winrtstoragehelper.codeplex.com/ )。
当从帧 1 导航到帧 2 或沿其他方向导航时,将调用并等待异步加载和安全操作。
事件处理程序变为异步返回 void =>他们不能被等待。
但是,该实用程序的第一个文件打开操作(可以说:在保存函数内)也是异步的
因此,第一个等待将控制权返回给框架,该框架稍后通过第二个事件处理程序调用另一个实用程序(加载)。
负载现在尝试打开同一个文件,如果
文件现在已打开以进行保存操作,失败并出现 ACCESSDENIED 异常。
对我来说,最低限度的解决方案是通过 using 和 AsyncLock 来保护文件访问。
请注意,他的锁基本上只用一把锁就锁定了实用程序的所有文件操作,这不必要地强大,但对于我的场景来说效果很好。
这里是我的测试项目:一个包含一些测试调用的 Windows 8 应用商店应用对于来自 http://winrtstoragehelper.codeplex.com/ 和我的修改版本,它使用 斯蒂芬·托布。
我还可以推荐这个链接吗:
http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx
This referes to Building Async Coordination Primitives, Part 6: AsyncLock , http://winrtstoragehelper.codeplex.com/ , Windows 8 app store and .net 4.5
Here is my angle on this:
The async/await language feature makes many things fairly easy but it also introduces a scenario that was
rarely encounter before it was so easy to use async calls: reentrance.
This is especially true for event handlers, because for many events you don't have any clue about whats happening after you return from the event handler.
One thing that might actually happen is, that the async method you are awaiting in the first event handler, gets called from another event handler still on the
same thread.
Here is a real scenario I came across in a windows 8 App store app:
My app has two frames: coming into and leaving from a frame I want to load/safe some data to file/storage.
OnNavigatedTo/From events are used for the saving and loading. The saving and loading is done by some async utility function (like http://winrtstoragehelper.codeplex.com/).
When navigating from frame 1 to frame 2 or in the other direction, the async load and safe operations are called and awaited.
The event handlers become async returning void => they cant be awaited.
However, the first file open operation (lets says: inside a save function) of the utility is async too
and so the first await returns control to the framework, which sometime later calls the other utility (load) via the second event handler.
The load now tries to open the same file and if
the file is open by now for the save operation, fails with an ACCESSDENIED exception.
A minimum solution for me is to secure the file access via a using and an AsyncLock.
Please note that his lock basically locks down all file operation for the utility with just one lock, which is unnecessarily strong but works fine for my scenario.
Here is my test project: a windows 8 app store app with some test calls for the original version from http://winrtstoragehelper.codeplex.com/ and my modified version that uses the AsyncLock from Stephen Toub.
May I also suggest this link:
http://www.hanselman.com/blog/ComparingTwoTechniquesInNETAsynchronousCoordinationPrimitives.aspx
Stephen Taub 已经实现了此问题的解决方案,请参阅 构建异步协调原语,第 7 部分:AsyncReaderWriterLock。
斯蒂芬·陶布(Stephen Taub)在业界享有盛誉,因此他写的任何内容都可能是可靠的。
我不会重现他在博客上发布的代码,但我将向您展示如何使用它:
如果您想要嵌入到 .NET 框架中的方法,请改用
SemaphoreSlim.WaitAsync
。您不会获得读取器/写入器锁,但您将获得经过尝试和测试的实现。Stephen Taub has implemented a solution to this question, see Building Async Coordination Primitives, Part 7: AsyncReaderWriterLock.
Stephen Taub is highly regarded in the industry, so anything he writes is likely to be solid.
I won't reproduce the code that he posted on his blog, but I will show you how to use it:
If you want a method that's baked into the .NET framework, use
SemaphoreSlim.WaitAsync
instead. You won't get a reader/writer lock, but you will get tried and tested implementation.嗯,看起来很丑,但似乎有用。
Hmm, looks ugly, seems to work.
我创建了一个 MutexAsyncable 类,灵感来自 Stephen Toub 的 AsyncLock 实现(讨论位于 这篇博文),它可以用作
lock
语句的直接替代同步或异步代码:如果您使用 .NET 5+,则可以安全地使用上述代码,因为它永远不会抛出 ThreadAbortException。
我还创建了一个扩展的 SemaphoreLocker 类,其灵感来自于这个答案,它可以是一个通用的 -
lock
的目的替换,可同步或异步使用。它比上面的MutexAsyncable
效率低,并且分配更多资源,尽管它具有强制工作代码在完成后释放锁的好处(从技术上讲,返回的IDisposable
由MutexAsyncable
无法通过调用代码释放并导致死锁)。它还具有额外的 try/finally 代码来处理 ThreadAbortException 的可能性,因此应该可以在早期的 .NET 版本中使用:I created a
MutexAsyncable
class, inspired by Stephen Toub's AsyncLock implementation (discussion at this blog post), which can be used as a drop-in replacement for alock
statement in either sync or async code:It's safe to use the above if you're using .NET 5+ because that won't ever throw
ThreadAbortException
.I also created an extended
SemaphoreLocker
class, inspired by this answer, which can be a general-purpose replacement forlock
, usable either synchronously or asynchronously. It is less efficient than the aboveMutexAsyncable
and allocates more resources, although it has the benefit of forcing the worker code to release the lock once it's finished (technically, theIDisposable
returned by theMutexAsyncable
could not get disposed by calling code and cause deadlock). It also has extra try/finally code to deal with the possibility ofThreadAbortException
, so should be usable in earlier .NET versions:我确实尝试使用
Monitor
(下面的代码),它似乎可以工作,但有一个问题......当你有多个线程时,它会给出......在此之前,我只是这样做,但它是在 ASP.NET 控制器中,因此导致了死锁。
I did try using a
Monitor
(code below) which appears to work but has a GOTCHA... when you have multiple threads it will give...Prior to this I was simply doing this, but it was in an ASP.NET controller so it resulted in a deadlock.
我有自己的 AsyncLock 类。
我将其设为 IDisposable,因此它可以使用 using 块。
(idispose中信号量的解锁确保出现异常时它也会被解锁,而不需要try-finally)
然后可以按如下方式使用
如您所见,只需替换lock中的new object()与 new SemaphoreSlim(1, 1) 的
lock-语句进行比较:
I have my own class AsyncLock.
I made it IDisposable, so it can use a using-block.
(the unlock of the semaphore in the idispose ensures it will also be unlocked if there is an exception, without the need for try-finally)
This can then be used as follows
As you can see, new object() from lock is simply replaced with new SemaphoreSlim(1, 1)
The lock-statement for comparison: