一尘不染

为什么不能在lock语句的主体内使用'await'运算符?

c#

锁定语句中不允许使用C#(.NET Async CTP)中的await关键字。

MSDN

等待表达式不能用于 同步函数,查询表达式,异常处理语句的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的调用似乎无限期地(大部分时间)阻塞,导致死锁,因为其他线程试图获取该锁。我怀疑我的工作不可靠,而锁语句中不允许使用await语句的原因与某种原因有关。

有谁知道 为什么 锁语句体内不允许等待?


阅读 829

收藏
2020-05-19

共1个答案

一尘不染

我认为由于某种原因,对于编译器团队而言,这既困难又不可能。

不,实施起来并非没有困难或不可能,您自己实施的事实就证明了这一事实。相反, 这是一个非常糟糕的主意 ,因此我们不允许这样做,以防止您犯此错误。

在DisposeDisposable.Dispose中对Monitor.Exit的调用似乎无限期地(大部分时间)阻塞,导致死锁,因为其他线程试图获取该锁。我怀疑我的工作不可靠,而锁语句中不允许使用await语句的原因与某种原因有关。

正确,您已经发现我们将其设为非法的原因。 在锁内部等待等待是产生死锁的秘诀。

我敢肯定,您会明白为什么: 在await将控制权返回给调用方与方法恢复之间的这段时间内,任意代码都会运行
。那个任意代码可能会取出产生锁顺序倒置的锁,从而导致死锁。

更糟糕的是, 代码可能会在另一个线程上恢复
(在高级方案中;通常您会在执行等待的线程上再次接听,但不一定),在这种情况下,解锁将解锁与所用线程不同的线程上的锁开锁。这是一个好主意吗?没有。

我注意到,出于相同的原因,对a进行yield return内部操作也是“最差的做法”
lock。这样做是合法的,但我希望我们将其定为非法。我们不会为“等待”犯同样的错误。

2020-05-19