如何找到哪种方法在异步/等待中“挂起”?

iro*_*nic 5 c# debugging asynchronous task hang

在过去,跟踪挂起的方法非常容易:只需转到调试器,单击“暂停”按钮并遍历堆栈跟踪即可。

但是,现在,如果问题出在异步方法中,则该方法不起作用-因为要执行的下一段代码埋在连续任务中的某个位置(从技术上讲,它甚至都没有挂起)...轻松调试任务?

UPD。

例:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

请注意,如果启动它并单击“暂停”,则只会看到DoHolyWar方法正在挂起,但看不到确切的位置。如果您用.Wait()替换'await',然后执行相同的操作,则可以检查挂起的堆栈跟踪。通过此示例,它非常简单,但是在实际应用中,通常很难找到问题所在。特别是,桌面应用程序将在主线程上运行事件循环,因此,即使某些事情“挂起”,而您单击“暂停”,也不会知道出了什么问题。

Sco*_*ain 3

在这种情况下,您可以做的是转到调试下拉菜单,转到Windows,然后选择“任务”窗口(默认快捷键组合是“ Ctrl+DK”)。

这可以让您了解哪些任务挂起。

在此输入图像描述

查找具有异常长Duration值的任务,这表明该任务发生了某些情况并且未完成。如果双击该行,它将带您进入挂起的等待。

我不确定为什么await BurnHeretics();没有显示在列表中,但我确实知道此窗口的行为会根据您的操作系统版本而有所不同,因为它依赖操作系统功能来跟踪某些类型的任务。但至少这会告诉您await DoHolyWarComplicatedDetails();正在挂起,这将导致您进行检查DoHolyWarComplicatedDetails(),这将导致您进行检查BurnHeretics();,这将导致您发现导致挂起的错误。

更新:我刚刚意识到它确实显示await BurnHeretics();为造成阻塞的主要原因。如果您查看该Task列,其<DoHolyWarComplicatedDetails>d__3含义是“在方法中DoHolyWarComplicatedDetails编译器生成的类<DoHolyWarComplicatedDetails>d__3被调度并正在等待信号到达”。这<DoHolyWarComplicatedDetails>d__3是 的状态机await BurnHeretics();,如果您使用像 DotPeek 这样的反编译器并允许显示编译器生成的代码,您可以看到它。

[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)
  {
  }
}
Run Code Online (Sandbox Code Playgroud)