Debugging exceptions in a Async/Await (Call Stack)

前端 未结 1 1485
天涯浪人
天涯浪人 2020-12-23 10:29

I use the Async/Await to free my UI-Thread and accomplish multithreading. Now I have a problem when I hit a exception. The Call Stack of my Async parts allways

相关标签:
1条回答
  • 2020-12-23 11:13

    The article you found does a good job of explaining why call stacks don't work the way most of us think they do. Technically, the call stack only tells us where the code is returning to after the current method. In other words, the call stack is "where the code is going", not "where the code came from".

    Interestingly, the article does mention a solution in passing, but doesn't expound on it. I have a blog post that goes explains the CallContext solution in detail. Essentially, you use the logical call context to create your own "diagnostic context".

    I like the CallContext solution better than the solution presented in the article because it does work will all forms of async code (including fork/join code like Task.WhenAll).

    This is the best solution I know of (other than doing something really complex like hooking into the profiling API). Caveats of the CallContext approach:

    • It only works on .NET 4.5 full. No support for Windows Store apps, .NET 4.0, etc.
    • You do have to "instrument" your code manually. There's no way AFAIK to inject it automatically.
    • Exceptions don't capture the logical call context automatically. So this solution works fine if you're breaking into the debugger when exceptions are thrown, but it's not as useful if you're just catching the exceptions in another place and logging them.

    The code (depends on the 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());
            }
        }
    }
    

    Usage:

    static async Task SomeWorkAsync()
    {
        using (MyStack.Push()) // Pushes "SomeWorkAsync"
        {
            ...
        }
    }
    

    Update: I released a NuGet package (described on my blog) that uses PostSharp to inject the pushes and pops automatically. So getting a good trace should be a lot simpler now.

    0 讨论(0)
提交回复
热议问题