【问题标题】:Debugging exceptions in a Async/Await (Call Stack)在异步/等待(调用堆栈)中调试异常
【发布时间】:2013-03-31 16:08:10
【问题描述】:

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

我找到了一篇关于它的 MSDN 文章 Andrew Stasyuk. Async Causality Chain Tracking,但据我了解,它不是一个现成的解决方案。

如果将多线程与 Async/Await 结合使用,最好/最简单的调试方法是什么?

【问题讨论】:

    标签: c# stack-trace async-await visual-studio-debugging c#-5.0


    【解决方案1】:

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

    有趣的是,文章确实顺便提到了一个解决方案,但没有详细说明。我有a blog post that goes explains the CallContext solution in detail。本质上,您使用逻辑调用上下文来创建自己的“诊断上下文”。

    我更喜欢CallContext 解决方案而不是文章中介绍的解决方案,因为它确实适用于所有形式的async 代码(包括像Task.WhenAll 这样的fork/join 代码)。

    这是我所知道的最好的解决方案(除了做一些非常复杂的事情,比如挂钩到分析 API 中)。 CallContext 方法的注意事项:

    • 它仅适用于 .NET 4.5 full。不支持 Windows 应用商店应用、.NET 4.0 等。
    • 您必须手动“检测”您的代码。 AFAIK 无法自动注入它。
    • 异常不会自动捕获逻辑调用上下文。因此,如果您在抛出异常时闯入调试器,则此解决方案可以正常工作,但如果您只是在另一个地方捕获异常并记录它们,则它就没有那么有用了。

    代码(取决于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 package (described on my blog),它使用 PostSharp 自动注入推送和弹出。所以现在获得一个好的跟踪应该容易得多。

    【讨论】:

    • 感谢您的快速答复。我会尝试看看它是如何工作的:-)。我以某种方式希望会有像Paralell Stack这样的解决方案,并提供更多信息。但你不能拥有一切;-)。我是否正确,只有MyStack.Push() 记录,如果我想知道我去过哪里,我到处都需要它?
    • MyStack.PushMyStack.Pop(处置)修改堆栈,是的,您需要在任何地方添加它们。 :( VS2012 是第一个支持async 的版本;希望我们以后能得到更好的调试支持!
    • 你拯救了我的一天。知道一个人来自哪里真的会对调试产生影响^^。我一直在寻找的错误几乎立即变得清晰;太糟糕了,如果不使用你的名字,我就无法在谷歌上找到你的博客条目。也许您可能想要进行谷歌优化,因为您的博客很好,并且有其他有用的条目我会在有时间时阅读。
    • 很高兴我能帮上忙!的确,我的博客根本没有 SEO,我真的应该这样做......
    • 更新:我发布了一个NuGet package (described on my blog),它使用 PostSharp 自动注入推送和弹出。所以现在获得一个好的跟踪应该容易得多。
    猜你喜欢
    • 2021-09-30
    • 2020-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-25
    • 1970-01-01
    • 2021-06-30
    • 2016-10-13
    相关资源
    最近更新 更多