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

Pat*_*ick 18 c# stack-trace visual-studio-debugging async-await c#-5.0

我使用Async/Await释放我的UI-Thread并完成多线程.我遇到异常时遇到问题.在Call Stack我的异步部分与百达开始ThreadPoolWorkQue.Dipatch(),这并不能帮助我非常多.

我找到了MSDN文章Andrew Stasyuk.Async Causality Chain跟踪它,但据我了解,它不是一个随时可用的解决方案.

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

Ste*_*ary 20

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

有趣的是,文章确实提到了一个解决方案,但没有对其进行阐述.我有一篇博文CallContext,详细解释了解决方案.实质上,您使用逻辑调用上下文来创建自己的"诊断上下文".

我喜欢这个CallContext解决方案比文章中提供的解决方案更好,因为它确实可以工作所有形式的async代码(包括fork/join代码Task.WhenAll).

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

  • 它仅适用于.NET 4.5完整版.不支持Windows应用商店应用,.NET 4.0等.
  • 您必须手动"检测"您的代码.AFAIK无法自动注入它.
  • 异常不会自动捕获逻辑调用上下文.因此,如果您在抛出异常时进入调试器,此解决方案可以正常工作,但如果您只是在其他位置捕获异常并记录它们,那么它就不那么有用了.

代码(取决于不可变集合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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

static async Task SomeWorkAsync()
{
    using (MyStack.Push()) // Pushes "SomeWorkAsync"
    {
        ...
    }
}
Run Code Online (Sandbox Code Playgroud)

更新:我发布了一个NuGet包(在我的博客上描述),它使用PostSharp自动注入推送和弹出.因此,获得良好的痕迹现在应该更加简单.