Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find which method 'hangs' with async/await?

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.

like image 598
ironic Avatar asked Aug 19 '16 13:08

ironic


1 Answers

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)
  {
  }
}
like image 128
Scott Chamberlain Avatar answered Nov 13 '22 05:11

Scott Chamberlain