Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debugging exceptions in a Async/Await (Call Stack)

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 starts with ThreadPoolWorkQue.Dipatch(), which doesn't help me very much.

I found a MSDN-Article Andrew Stasyuk. Async Causality Chain Tracking about it but as I understand it, its not a ready to use solution.

What is the best/easiest way to debug if you use multithreading with Async/Await?

like image 614
Patrick Avatar asked Apr 09 '13 09:04

Patrick


People also ask

What is call stack in debugging?

The call stack is a list of all the active functions that have been called to get to the current point of execution. The call stack includes an entry for each function called, as well as which line of code will be returned to when the function returns.

How to debug Async Await javascript?

Since asynchronous code's output isn't sequential, tick the “Pause on Exception” button when debugging. This will pause execution whenever you get an exception. Use the step over button when you get an exception to inspect the entire call stack. The first step to debugging asynchronous code is to use the console.

Can we debug Async method?

Debugging asynchronous code is a challenge because the tasks are often scheduled in one thread and executed in another. Every thread has its own stacktrace, making it difficult to figure out what happened before the thread started.

How do I debug async in Chrome?

# Enable async debugging in ChromeGo to the Sources panel of Chrome Canary DevTools. Next to the Call Stack panel on the right hand side, there is a new checkbox for "Async". Toggle the checkbox to turn async debugging on or off. (Although once it's on, you may not ever want to turn it off.)


1 Answers

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.

like image 170
Stephen Cleary Avatar answered Oct 21 '22 17:10

Stephen Cleary