为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

慕后森 2019-10-15 09:24:16
锁定语句中不允许使用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 usageusing (await Async.Lock(padlock)){    await SomethingAsync();}但是,这无法正常工作。在ExitDisposable.Dispose中对Monitor.Exit的调用似乎无限期地(大部分时间)阻塞,导致死锁,因为其他线程试图获取该锁。我怀疑我的工作不可靠,而锁语句中不允许使用await语句的原因与某种原因有关。有谁知道为什么锁语句体内不允许等待?
查看完整描述

3 回答

?
千巷猫影

TA贡献1829条经验 获得超7个赞

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


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


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


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


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


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


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


查看完整回答
反对 回复 2019-10-15
?
蓝山帝景

TA贡献1843条经验 获得超7个赞

使用SemaphoreSlim.WaitAsync方法。


 await mySemaphoreSlim.WaitAsync();

 try {

     await Stuff();

 } finally {

     mySemaphoreSlim.Release();

 }


查看完整回答
反对 回复 2019-10-15
?
慕丝7291255

TA贡献1859条经验 获得超6个赞

基本上,这将是错误的事情。


有两个方面,这可以实现:


保持锁,只在块的末尾释放它。

这是一个非常糟糕的主意,因为您不知道异步操作将花费多长时间。您只应在最短时间内握住锁。这也可能是不可能的,因为线程拥有一个锁,而不是一个方法-并且您甚至可能没有在同一线程上执行其余的异步方法(取决于任务调度程序)。


释放等待中的锁,并在等待返回时重新获取它。

这违反了IMO最小原则,在IMO中,异步方法的行为应与等效的同步代码尽可能接近-除非您Monitor.Wait在锁块中使用,否则您希望在该区块的持续时间内拥有该锁。


因此,这里基本上有两个相互竞争的需求-您不应该在这里尝试第一个,而如果您想采用第二种方法,则可以通过用await表达式分隔两个分开的锁块来使代码更清晰:


// Now it's clear where the locks will be acquired and released

lock (foo)

{

}

var result = await something;

lock (foo)

{

}

因此,通过禁止您等待锁块本身,该语言迫使您考虑自己真正想做的事情,并在编写的代码中使选择更清晰。


查看完整回答
反对 回复 2019-10-15
  • 3 回答
  • 0 关注
  • 3027 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信