我使用 Async/Await 来释放我的 UI 线程并完成多线程。现在,当我遇到异常时,我遇到了问题。我的Call Stack
Async 部分总是以 开头ThreadPoolWorkQue.Dipatch()
,这对我没有多大帮助。
我找到了一篇 MSDN 文章Andrew Stasyuk。关于它的异步因果链跟踪,但据我了解,它不是一个现成的解决方案。
如果将多线程与 Async/Await 一起使用,最好/最简单的调试方法是什么?
我使用 Async/Await 来释放我的 UI 线程并完成多线程。现在,当我遇到异常时,我遇到了问题。我的Call Stack
Async 部分总是以 开头ThreadPoolWorkQue.Dipatch()
,这对我没有多大帮助。
我找到了一篇 MSDN 文章Andrew Stasyuk。关于它的异步因果链跟踪,但据我了解,它不是一个现成的解决方案。
如果将多线程与 Async/Await 一起使用,最好/最简单的调试方法是什么?
您找到的文章很好地解释了为什么调用堆栈不像我们大多数人认为的那样工作。从技术上讲,调用堆栈只告诉我们代码在当前方法之后返回到哪里。换句话说,调用堆栈是“代码要去哪里”,而不是“代码来自哪里”。
有趣的是,文章确实顺便提到了一个解决方案,但没有详细说明。我有一篇博文CallContext
详细解释了解决方案。本质上,您使用逻辑调用上下文来创建自己的“诊断上下文”。
我CallContext
比文章中介绍的解决方案更喜欢该解决方案,因为它确实适用于所有形式的async
代码(包括 fork/join 之类的代码Task.WhenAll
)。
这是我所知道的最好的解决方案(除了做一些非常复杂的事情,比如挂钩到分析 API 中)。该方法的注意事项CallContext
:
代码(取决于不可变集合 NuGet 库):
public static class MyStack
{
private static readonly string name = Guid.NewGuid().ToString("N");
private static ImmutableStack<string> CurrentContext
{
get
{
var ret = CallContext.LogicalGetData(name) as ImmutableStack<string>;
return ret ?? ImmutableStack.Create<string>();
}
set
{
CallContext.LogicalSetData(name, value);
}
}
public static IDisposable Push([CallerMemberName] string context = "")
{
CurrentContext = CurrentContext.Push(context);
return new PopWhenDisposed();
}
private static void Pop()
{
CurrentContext = CurrentContext.Pop();
}
private sealed class PopWhenDisposed : IDisposable
{
private bool disposed;
public void Dispose()
{
if (disposed)
return;
Pop();
disposed = true;
}
}
// Keep this in your watch window.
public static string CurrentStack
{
get
{
return string.Join(" ", CurrentContext.Reverse());
}
}
}
用法:
static async Task SomeWorkAsync()
{
using (MyStack.Push()) // Pushes "SomeWorkAsync"
{
...
}
}
更新:我发布了一个NuGet 包(在我的博客中描述),它使用 PostSharp 自动注入推送和弹出。因此,现在获得良好的跟踪应该要简单得多。