如何找到 'hangs' 与 async/await 的方法?
How to find which method 'hangs' with async/await?
在 'old' 次,跟踪挂起的方法非常容易:只需转到调试器,点击 'pause' 按钮并查看堆栈跟踪。
但是现在,如果问题出在异步方法中,则此方法不起作用 - 因为下一段要执行的代码被埋在后续任务中的某个地方(从技术上讲,它甚至不会挂起)...是有没有一种方法可以轻松调试任务?
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;
}
}
请注意,如果您启动它并点击 'pause',您只会看到 DoHolyWar 方法挂起,但看不到确切位置。如果您将 'await' 替换为 .Wait(),并执行相同操作,您将能够检查挂起的堆栈跟踪。这个例子很简单,但在实际应用中,通常很难找到问题所在。特别是桌面应用程序将在主线程上有事件循环 运行,因此即使某些事情发生 'hang',并且您点击 'pause',您也不会知道哪里出了问题。
在这种情况下,您可以转到调试下拉菜单,转到 Windows,然后选择 "Tasks" window(默认的快捷键组合是“Ctrl+D, K”).
这可以让您了解哪些任务挂起。
寻找具有异常长 Duration
值的任务,这表明任务发生了某些事情并且没有完成。如果您双击该行,它将带您到挂起的等待。
我不确定为什么 await BurnHeretics();
没有出现在我的列表中,但我知道这个 window 会根据您的 OS 表现不同版本,因为它依赖 OS 功能来跟踪某些类型的任务。但至少这会告诉您 await DoHolyWarComplicatedDetails();
挂起,这将引导您检查 DoHolyWarComplicatedDetails()
,这将引导您检查 BurnHeretics();
,这将引导您找到导致挂起的错误。
UPDATE:我刚刚意识到它确实显示 await BurnHeretics();
作为导致该块的主要因素。如果查看 Task
列, <DoHolyWarComplicatedDetails>d__3
表示 "in the method DoHolyWarComplicatedDetails
the compiler generated class <DoHolyWarComplicatedDetails>d__3
is scheduled and is waiting for a signal to arrive." <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)
{
}
}
在 'old' 次,跟踪挂起的方法非常容易:只需转到调试器,点击 'pause' 按钮并查看堆栈跟踪。
但是现在,如果问题出在异步方法中,则此方法不起作用 - 因为下一段要执行的代码被埋在后续任务中的某个地方(从技术上讲,它甚至不会挂起)...是有没有一种方法可以轻松调试任务?
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;
}
}
请注意,如果您启动它并点击 'pause',您只会看到 DoHolyWar 方法挂起,但看不到确切位置。如果您将 'await' 替换为 .Wait(),并执行相同操作,您将能够检查挂起的堆栈跟踪。这个例子很简单,但在实际应用中,通常很难找到问题所在。特别是桌面应用程序将在主线程上有事件循环 运行,因此即使某些事情发生 'hang',并且您点击 'pause',您也不会知道哪里出了问题。
在这种情况下,您可以转到调试下拉菜单,转到 Windows,然后选择 "Tasks" window(默认的快捷键组合是“Ctrl+D, K”).
这可以让您了解哪些任务挂起。
寻找具有异常长 Duration
值的任务,这表明任务发生了某些事情并且没有完成。如果您双击该行,它将带您到挂起的等待。
我不确定为什么 await BurnHeretics();
没有出现在我的列表中,但我知道这个 window 会根据您的 OS 表现不同版本,因为它依赖 OS 功能来跟踪某些类型的任务。但至少这会告诉您 await DoHolyWarComplicatedDetails();
挂起,这将引导您检查 DoHolyWarComplicatedDetails()
,这将引导您检查 BurnHeretics();
,这将引导您找到导致挂起的错误。
UPDATE:我刚刚意识到它确实显示 await BurnHeretics();
作为导致该块的主要因素。如果查看 Task
列, <DoHolyWarComplicatedDetails>d__3
表示 "in the method DoHolyWarComplicatedDetails
the compiler generated class <DoHolyWarComplicatedDetails>d__3
is scheduled and is waiting for a signal to arrive." <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)
{
}
}