在有 UI 线程参与的同步锁(如 AutoResetEvent)内部使用 await 可能导致死锁


AutoResetEventManualResetEventMonitorlock 等等这些用来做同步的类,如果在异步上下文(await)中使用,需要非常谨慎。

本文将说一个在同步上下文中非常常见的一种用法,换成异步上下文中会产生死锁的问题。


本文内容

      • 一段正常的同步上下文的代码
      • 一个微调即会死锁
      • 此死锁的触发条件
      • 此死锁的原因
      • 更多死锁问题

出让执行权:Task.Yield, Dispatcher.Yield - walterlv 一问中有说到它的原理。

await 等待完成之后,会调用 BeginInvoke 回到 UI 线程。然而,此时 UI 线程正卡死在 _resetEvent.WaitOne();,于是根本没有办法执行 BeginInvoke 中的操作,也就是 await 之后的代码。然而释放锁的代码 _resetEvent.Set(); 就在 await 之后,所以不会执行,于是死锁。

使用 Task.Wait()?立刻死锁(deadlock) - walterlv
  • 不要使用 Dispatcher.Invoke,因为它可能在你的延迟初始化 Lazy 中导致死锁 - walterlv
  • 在有 UI 线程参与的同步锁(如 AutoResetEvent)内部使用 await 可能导致死锁
  • .NET 中小心嵌套等待的 Task,它可能会耗尽你线程池的现有资源,出现类似死锁的情况 - walterlv
  • 解决方法:

    • 在编写异步方法时,使用 ConfigureAwait(false) 避免使用者死锁 - walterlv
    • 将 async/await 异步代码转换为安全的不会死锁的同步代码(使用 PushFrame) - walterlv