在异步/等待(调用堆栈)中调试异常

19

我使用Async/Await来释放UI线程并实现多线程。现在我遇到一个问题,当我遇到异常时,我的异步部分的调用堆栈(Call Stack)总是以ThreadPoolWorkQue.Dipatch()开头,这对我没有帮助。

我发现了一篇MSDN文章Andrew Stasyuk. Async Causality Chain Tracking,但我理解它并不是一个成熟的解决方案。

如果你使用Async/Await进行多线程编程,最佳/最简单的debug方法是什么?

1个回答

20

这篇文章很好地解释了为什么调用栈不像我们大多数人想象的那样工作。从技术上讲,调用栈只告诉我们当前方法返回后代码要到哪里。换句话说,调用栈是“代码去哪里”,而不是“代码来自哪里”。

有趣的是,这篇文章在提及解决方案时顺便提到了一个解决方案,但并没有详细解释。我写了一篇博客文章详细介绍了CallContext解决方案。实质上,您可以使用逻辑调用上下文创建自己的“诊断上下文”。

我喜欢CallContext解决方案胜过文章中提出的解决方案,因为它适用于所有形式的async代码(包括类似Task.WhenAll的fork/join代码)。

这是我所知道的最好的解决方案(除了像钩入分析API这样的复杂操作)。CallContext方法的注意事项如下:

  • 它仅适用于.NET 4.5完整版。不支持Windows Store应用程序,.NET 4.0等。
  • 您必须手动“调整”您的代码。我不知道有没有自动注入的方法。
  • 异常不会自动捕获逻辑调用上下文。因此,如果在抛出异常时进入调试器,则该解决方案可以很好地工作,但如果您只是在其他地方捕获异常并记录它们,则不太有用。

代码(依赖于immutable collections NuGet library):

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自动注入推送和弹出。现在获取良好的跟踪应该会更加简单。


谢谢您的快速回复。我会尝试并查看它是如何工作的 :-)。我有些希望有一个像Paralell Stack这样能提供更多信息的解决方案。但你不能拥有一切 ;-)。如果我想知道自己去过哪里,只有MyStack.Push()日志记录,我是否正确? - Patrick
"MyStack.Push" 和 "MyStack.Pop"(处理)会修改堆栈,是的,你需要在所有地方添加它们。 :( VS2012 是第一个支持 "async" 的版本;希望我们将来能获得更好的调试支持! - Stephen Cleary
你救了我的一天。知道代码从哪里来真的可以帮助调试。我已经搜索了几天的bug几乎瞬间变得清晰明了; 只是很遗憾,我无法在谷歌上找到您的博客文章,除非使用您的姓名。也许您应该优化一下您的谷歌搜索,因为您的博客非常好,并有其他有用的条目,我会在找到时间时阅读它们。 - Patrick
很高兴能帮忙!我的博客确实没有进行任何SEO优化,我真的应该做一下... - Stephen Cleary
更新:我发布了一个NuGet包(在我的博客上有描述),它使用PostSharp自动注入推送和弹出。因此,现在获取良好的跟踪应该更加简单。 - Stephen Cleary

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接