1

I have this code:

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

I know this is not the right way to do it. Here is what I think is probably the right way:

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

I'm just wondering, though, why does it hang indefinitely on t.Wait() when I run the first code example? What exactly is happening to the code execution at that point, i.e. what code is "running" when the thread is blocked on t.Wait()?

rory.ap
  • 34,009
  • 10
  • 83
  • 174
  • possible duplicate of [Synchronously waiting for an async operation, and why does Wait() freeze the program here](http://stackoverflow.com/questions/14485115/synchronously-waiting-for-an-async-operation-and-why-does-wait-freeze-the-pro) – Scott Chamberlain Jan 15 '15 at 23:02

2 Answers2

6

Let's get the practical out of the way first: yes, you should be Awaiting instead of Wait()ing on the result. Now on to your question of why this is happening...

In Stephen Cleary's blog post Don't Block on Async Code, he explains how this happens. Here's his "what happens" walkthrough, modified to your methods:

  1. The top-level method Button1_Click calls DoStuffAsync (within the UI context).
  2. DoStuffAsync calls Task.Delay (still within the context).
  3. Task.Delay returns an uncompleted Task, indicating the time has not yet elapsed.
  4. DoStuffAsync awaits the Task returned by Task.Delay. The context is captured and will be used to continue running the DoStuffAsync method later. DoStuffAsync returns an uncompleted Task, indicating that the DoStuffAsync method is not complete.
  5. The top-level method synchronously blocks on the Task returned by DoStuffAsync. This blocks the context thread.
  6. … Eventually, the specified time will elapse and the Delay task will complete.
  7. The continuation for DoStuffAsync is now ready to run, and it waits for the context to be available so it can execute in the context.
  8. Deadlock. The top-level method is blocking the context thread, waiting for DoStuffAsync to complete, and DoStuffAsync is waiting for the context to be free so it can complete.

In this UI context, that also happens to be a particular thread, but this needn't be the cast. For example, in ASP.NET there's a context that can run on any thread, but only on one thread at a time. The important thing is that the context is locked, regardless of what threads it might actually run on.

Tim S.
  • 55,448
  • 7
  • 96
  • 122
  • Thanks for this, it was very informative. I've been reading a lot of Stephen Cleary lately, but I hadn't come across that one yet. – rory.ap Jan 16 '15 at 00:36
1

It's deadlocking. You're running Task.Delay on the UI thread. Then you block the UI thread waiting for Task.Delay to finish. You're correct: You should be awaiting the Task.

Daniel Mann
  • 57,011
  • 13
  • 100
  • 120
  • Okay, so I started getting confused when I was reading over [this post](http://stackoverflow.com/questions/19415646/should-i-avoid-async-void-event-handlers). It looks like the OP's example code beneath "I can rewrite it like this:" would suffer from the same condition as my issue, but Eric Lippert said in his answer "Now, if you do need to track the task for some reason then the technique you describe is perfectly reasonable." Where am I getting mixed up? – rory.ap Jan 15 '15 at 22:07
  • 1
    The `Wait` method is what's causing the problem. The question you were reading was about the use cases of `async void` methods and isn't calling `Wait`. – Daniel Mann Jan 15 '15 at 22:14