为什么我不能在锁语句的主体中使用 'await' 运算符?
为什么我不能在锁语句的主体中使用 'await' 运算符?
在C# (.NET Async CTP)中,await
关键字不允许在lock
语句内使用。\n根据MSDN的说明:\n
\n在同步函数、查询表达式、异常处理语句的catch或finally块、
lock
语句的块或不安全上下文中,不能使用await
表达式。\n
\n我认为编译器团队在某些情况下很难或不可能实现这一点。\n我尝试了一种使用using语句的解决方法:\n
class Async { public static async TaskLock(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); } } } // 示例用法 using (await Async.Lock(padlock)) { await SomethingAsync(); }
\n然而,这并不像预期的那样工作。在ExitDisposable.Dispose
中调用Monitor.Exit
似乎会无限期地阻塞(大部分时间),导致其他线程在尝试获取锁时出现死锁。我怀疑我解决方法的不可靠性和await
语句不允许在lock
语句中的原因可能有关。\n有人知道为什么await
不能在lock
语句的主体中使用吗?
为什么在锁语句的主体中不能使用'await'操作符?
在给出的代码示例中,SemaphoreLocker类提供了一个异步锁定方法LockAsync,该方法接受一个返回Task的委托作为参数。这个方法使用SemaphoreSlim类来实现异步锁定,以确保同一时间只有一个线程可以访问资源。在锁定期间,可以在LockAsync的委托参数中使用异步调用,以处理资源。
然而,在LockAsync方法中使用'await'操作符是不允许的。原因是,锁语句需要在同步上下文中执行,而'await'操作符会将代码的执行切换到异步上下文中。这意味着在锁语句中使用'await'操作符可能导致死锁,因为锁定期间的线程可能会在异步操作完成之前释放锁。
为了解决这个问题,代码示例中提供了一个修改后的LockAsync方法。在这个修改后的方法中,使用一个循环来尝试获取锁,并在一定的时间内等待锁的释放。如果在规定的时间内没有获取到锁,就放弃获取锁的尝试。这样可以避免在异步操作完成之前释放锁,从而防止死锁的发生。
此外,代码示例还提供了一个重载的LockAsync方法,可以处理返回类型为T的非void方法。这个重载方法使用相同的锁定机制,并在finally块中释放锁。使用这个重载方法可以在LockAsync的委托参数中获取返回值,并在finally块中返回。
不能在锁语句的主体中使用'await'操作符是为了防止死锁的发生。为了解决这个问题,可以使用SemaphoreSlim类的WaitAsync方法和一个循环来实现异步锁定,并在一定的时间内等待锁的释放。通过将锁定方法重载为处理返回值的非void方法,可以在锁定期间获取返回值并在finally块中返回。
在使用lock语句的代码块中,无法使用await操作符的原因是,lock语句在内部使用了信号量(Semaphore)来实现锁定的机制。而异步操作(async/await)涉及到等待任务的完成,这可能会导致在等待期间其他尝试获取锁的任务无法继续执行。这会导致一些任务长时间处于等待状态,造成效率低下。
为了解决这个问题,可以使用SemaphoreSlim类的WaitAsync方法来实现锁定并等待的操作。以下是示例代码:
await mySemaphoreSlim.WaitAsync(); try { await Stuff(); } finally { mySemaphoreSlim.Release(); }
SemaphoreSlim类是在最近引入到.NET框架中的,因此可以认为在异步/等待的环境中使用信号量来实现锁定的概念是经过良好验证的。
然而,有人提出了在try块中使用await是否是一个好主意的问题。如果Stuff是一个耗时的操作,比如进行HTTP请求,它可能会持有信号量几秒钟。在此期间,其他调用WaitAsync的任务将无法继续执行。虽然这可能不会导致响应性问题,因为它们立即将控制权让给调用者,但是会有一堆任务长时间处于空闲状态。
如果所有这些任务都在等待Stuff的结果,我不认为有任何解决办法...
为了获取更多信息,可以在这篇文章中搜索"SemaphoreSlim"文本:Async/Await - Best Practices in Asynchronous Programming。
还有人认为应该将SemaphoreSlim初始化为mySemaphoreSlim = new SemaphoreSlim(1, 1),以便像lock(...)一样工作。
总之,使用SemaphoreSlim类的WaitAsync方法可以在异步/等待的环境中实现锁定操作。这种方法可以解决在lock语句中无法使用await操作符的问题,并提供了一种有效的同步机制。
为什么在锁语句的主体中不能使用'await'运算符?
在上述内容中,我们可以得出以下结论:
1. 'await'运算符不能在锁语句的主体中使用,是因为这样做可能导致死锁。
2. 在'await'返回控制权给调用者和方法恢复之间,可以运行任意代码,这些代码可能会导致死锁。
3. 'await'运算符可能会在另一个线程上恢复执行,这将导致解锁操作在不同的线程上执行,可能会产生不可预测的结果。
4. 在异步上下文中,可能会出现奇怪的重入问题,因为线程被重用,导致多个任务在同一个锁块内执行。
为了解决这个问题,可以使用其他协调原语,如SemaphoreSlim、AsyncLock等。虽然它们不是.NET框架的核心组件,但仍然可以在外部命名空间中找到。还可以参考一些博客或文章中提供的解决方案。
总之,不能在锁语句的主体中使用'await'运算符是为了避免死锁和其他潜在的线程安全问题。