为什么等待代码会无限期冻结?

Why does awaitable code freeze indefinitely?

我有这个代码:

Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim t = DoStuffAsync()
    t.Wait()
    Debug.Print(t.Result)
End Sub

Private Async Function DoStuffAsync() As Task(Of String)
    Await Task.Delay(2000)
    Return "Stuff"
End Function

我知道这不是正确的做法。以下是我认为可能是正确的方法:

Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
    Dim t = DoStuffAsync()
    Debug.Print(Await t)
End Sub

Private Async Function DoStuffAsync() As Task(Of String)
    Await Task.Delay(2000)
    Return "Stuff"
End Function

不过,我只是想知道,为什么当我 运行 第一个代码示例时它会无限期地挂在 t.Wait() 上? 确切地 在代码执行时发生了什么,即当线程在 t.Wait() 上被阻塞时 "running" 是什么代码?

陷入僵局。您在 UI 话题中 运行 Task.Delay。然后阻塞 UI 线程等待 Task.Delay 完成。你是对的:你应该 awaiting Task.

让我们先从实际出发:是的,您应该 Awaiting 而不是 Wait()ing 结果。现在回答你为什么会这样的问题......

Stephen Cleary's blog post Don't Block on Async Code 中,他解释了这是如何发生的。这是他的 "what happens" 演练,根据您的方法进行了修改:

  1. 顶级方法 Button1_Click 调用 DoStuffAsync(在 UI 上下文中)。
  2. DoStuffAsync 调用 Task.Delay(仍在上下文中)。
  3. Task.Delay returns一个未完成的任务,表示时间还没有过去。
  4. DoStuffAsync 等待 Task.Delay 返回的 Task。上下文已捕获,稍后将用于继续 运行ning DoStuffAsync 方法。 DoStuffAsync returns一个未完成的Task,说明DoStuffAsync方法不完整
  5. 顶层方法同步阻塞 DoStuffAsync 返回的 Task。这会阻塞上下文线程。
  6. …最终,指定的时间将过去,Delay 任务将完成。
  7. DoStuffAsync 的延续现在已准备好 运行,它等待上下文可用以便它可以在上下文中执行。
  8. 僵局。顶级方法正在阻塞上下文线程,等待 DoStuffAsync 完成,并且 DoStuffAsync 正在等待上下文空闲以便它可以完成。

在此 UI 上下文中,这也恰好是一个特定的线程,但这不一定是强制转换。例如,在 ASP.NET 中有一个上下文可以 运行 在任何线程上,但一次只能在一个线程上。重要的是上下文是锁定的,不管它实际上 运行 在什么线程上。