8

In the 'old' times it was very easy to track which method is hanging: just go to debugger, hit 'pause' button and go through stack traces.

Now however, if the problem is in the async method, this approach does not work - since the next piece of code to execute is buried somewhere in the continuation tasks (technically it does not even hang)... Is there a way for such easy debugging with tasks?

UPD.

Example:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();           
    }

    private async void MainWindow_OnLoaded(object sender, RoutedEventArgs e)
    {
        await DoHolyWar();
        MessageBox.Show("Holy war complete!");
    }

    public static async Task DoHolyWar()
    {
        await DoHolyWarComplicatedDetails();
        Console.WriteLine("Victory!");
    }

    public static async Task DoHolyWarComplicatedDetails()
    {
        await BurnHeretics();
    }

    public static Task BurnHeretics()
    {
        var tcs = new TaskCompletionSource<object>();

        // we should have done this, but we forgot
        // tcs.SetResult(null);

        return tcs.Task;
    }
}

Notice that if you start it and hit 'pause' you will only see that DoHolyWar method is hanging, but you will not see where exactly. While if you replace 'await's with .Wait(), and do the same you will be able to examine the hanging stack trace. With this example it is quite simple, but in a real-world application it is often quite hard to find the problem. In particular a desktop application will have events loop running on the main thread, so even if something does 'hang', and you hit 'pause' you will get no clue about what went wrong.

Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
ironic
  • 8,368
  • 7
  • 35
  • 44
  • As a general rule, it's almost always the one that tries to act as if async doesn't exist by explicitly calling `Wait` or `Result`. – Damien_The_Unbeliever Aug 19 '16 at 13:49
  • Ah, ok. So it is not really a "Hang" as your program is still responsive (if you had done a WPF or winforms program and did a `await DoHolyWar()` the program would still be responsive, you may want to change your question to do that instead of being a console app to make it more clear that you are not talking about a actual "hang"), this is finding out that a `SetResult` is never called. – Scott Chamberlain Aug 19 '16 at 14:05
  • 1
    In fact, I updated your question to do that, if you don't like it feel free to roll back the change. – Scott Chamberlain Aug 19 '16 at 14:10
  • 1
    Ideally, for situations like this, I think you'd want to find `TaskCompletionSource` objects that are collected/are eligible for collection where none of the `SetXXX` methods have been called on them. A `TCS` leak, as it were (since the associated `Task` will never complete). Not sure if there is such a thing, nor whether it would be possible to build one. – Damien_The_Unbeliever Aug 19 '16 at 14:15
  • @Damien_The_Unbeliever, for the sake of completeness, do you know if TCS is the only source that can create such never-completing tasks? Besides, I can imagine a situation where, for example, network interaction is hanging(taking very long, etc), so TCS will actually still not be eligible for collection, while it would be very nice to be able to see a 'hanging' stack trace... – ironic Aug 19 '16 at 14:27
  • 1
    @ironic network interactions almost always have timeouts for this exact reason. Baring you set `Timeout.Infinite` on something, TCS is the likely only candidate for this happening. – Scott Chamberlain Aug 19 '16 at 14:30
  • This is somewhat related to another question https://stackoverflow.com/questions/41476418/how-to-find-out-deadlocking-awaited-tasks-and-their-current-call-stack – Steven Bone Jul 19 '19 at 19:51

1 Answers1

3

In situations like this, what you can do is go to the debug dropdown menu, go to Windows, and choose the "Tasks" window (The default short cut key combo is "Ctrl+D, K").

This can give you a clue on what tasks are hanging.

enter image description here

Look for tasks that have abnormally long Duration values, that is a indication that something happened to the task and it is not completing. If you double click on the line it will take you to the await that is hung.

I don't know for sure why await BurnHeretics(); does not show up in the list for me, but I do know that this window will behave differently depending on your OS version because it relies on OS features to track some types of tasks. But at minimum this will show you that await DoHolyWarComplicatedDetails(); is hanging which will lead you to inspect DoHolyWarComplicatedDetails() which will lead you to inspect BurnHeretics(); which will lead you to your bug that is causing the hang.

UPDATE: I just realized it does show await BurnHeretics(); as the main thing causnig the block. If you look at the Task column, the <DoHolyWarComplicatedDetails>d__3 means "in the method DoHolyWarComplicatedDetails the compiler generated class <DoHolyWarComplicatedDetails>d__3 is scheduled and is waiting for a signal to arrive." the <DoHolyWarComplicatedDetails>d__3 is the state machine for await BurnHeretics();, you can see it if you use a decompiler like DotPeek and allow show compiler generated code.

[CompilerGenerated]
private sealed class <DoHolyWarComplicatedDetails>d__3 : IAsyncStateMachine
{
  public int <>1__state;
  public AsyncTaskMethodBuilder <>t__builder;
  private TaskAwaiter <>u__1;

  public <DoHolyWarComplicatedDetails>d__3()
  {
    base..ctor();
  }

  void IAsyncStateMachine.MoveNext()
  {
    int num1 = this.<>1__state;
    try
    {
      TaskAwaiter awaiter;
      int num2;
      if (num1 != 0)
      {
        awaiter = MainWindow.BurnHeretics().GetAwaiter();
        if (!awaiter.IsCompleted)
        {
          this.<>1__state = num2 = 0;
          this.<>u__1 = awaiter;
          MainWindow.<DoHolyWarComplicatedDetails>d__3 stateMachine = this;
          this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter, MainWindow.<DoHolyWarComplicatedDetails>d__3>(ref awaiter, ref stateMachine);
          return;
        }
      }
      else
      {
        awaiter = this.<>u__1;
        this.<>u__1 = new TaskAwaiter();
        this.<>1__state = num2 = -1;
      }
      awaiter.GetResult();
      awaiter = new TaskAwaiter();
      Console.WriteLine("Heretics burned");
    }
    catch (Exception ex)
    {
      this.<>1__state = -2;
      this.<>t__builder.SetException(ex);
      return;
    }
    this.<>1__state = -2;
    this.<>t__builder.SetResult();
  }

  [DebuggerHidden]
  void IAsyncStateMachine.SetStateMachine(IAsyncStateMachine stateMachine)
  {
  }
}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431