Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting a meaningful stack trace when using async code

Tags:

c#

async-await

I've created a small bit of code for running multiple async operations in parallel (the Parallel class itself isn't good for async operations).

It looks like this:

public static async Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
    var chunks = source.Chunk(dop);
    foreach (var chunk in chunks)
        await Task.WhenAll(chunk.Select(async s => await body(s).ContinueWith(t => ThrowError(t))));
}

private static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
    while (source.Any())
    {
        yield return source.Take(chunksize);
        source = source.Skip(chunksize);
    }
}

private static void ThrowError(Task t)
{
    if (t.IsFaulted)
    {
        if (t.Exception.InnerExceptions != null && t.Exception.InnerExceptions.Count == 1)
            throw t.Exception.InnerExceptions[0];
        else
            throw t.Exception;
    }
}

As far as running the tasks in parallel goes, the above code works really well. I do, however, have some issues when exceptions are thrown.

The exception-capturing code works well as far as returning the exception message goes, but the stack trace leaves a lot to be desired - as it points to the ThrowError method, rather than the method that originally generated the exception. I can sort of work my way and figure out what went wrong with a debugger attached, but if I ever release this application I won't have that option available - at best, I'll have the exception with the stack trace logged.

So - is there any way to get a more meaningful stack trace when running async tasks?

PS. This is for a WindowsRT application but I think the problem isn't limited to WindowsRT as such...

like image 921
Shaamaan Avatar asked Nov 30 '15 09:11

Shaamaan


People also ask

What is async stack traces?

Asynchronous stack traces allow you to inspect function calls beyond the current event loop. This is particularly useful because you can examine the scope of previously executed frames that are no longer on the event loop. This feature is currently an experiment and needs to be enabled.

How do you analyze a stack trace?

Analyze external stack tracesFrom the main menu, select Code | Analyze Stack Trace or Thread Dump. In the Analyze Stack Trace dialog that opens, paste the external stack trace or thread dump into the Put a stacktrace here: text area. Click OK. The stack trace is displayed in the Run tool window.

How is stack trace generated?

The stack trace contains all invocations from the start of a thread until the point it's generated. This is usually a position at which an exception takes place. When printed out, the generation point shows up first, and method invocations leading to that point are displayed underneath.

What is a stack trace and what is it useful for?

A stack trace is a report that provides information about program subroutines. It is commonly used for certain kinds of debugging, where a stack trace can help software engineers figure out where a problem lies or how various subroutines work together during execution.

Do I need a stack trace for my async methods?

Your async methods do have to "opt in", though, so you don't get it for free like you would with a stack trace. I just wrote this up in a blog post that is not yet published, so you're getting a preview. :) You can build a "stack" of calls yourself around the logical call context as such:

What is a stack trace and how is it used?

Also, the stack trace is technically about where the code is returning to, not where the code came from. With simple (synchronous) code, the two are the same: the code always returns to whatever method called it.

How do I know if an async method is running?

If an async method is “active” (i.e. it is running on a thread), these logical stacks are sometimes displayed in the Threads view. However, the Tasks view will show both active and awaiting logical stacks. This is helpful for debugging hangs or figuring out why your particular task or code block is not running.

What does it mean when a method is marked as async?

To reiterate, marking a method as async means it will be transformed into a State Machine by the compiler. This certainly brings with itself some time & space execution overhead. However, except for some sporadic cases, this should be pretty much unnoticeable.


3 Answers

So - is there any way to get a more meaningful stack trace when running async tasks?

Yes, you can use ExceptionDispatchInfo.Capture that was introduced in .NET 4.5 for async-await speficially:

private static void ThrowError(Task t)
{
    if (t.IsFaulted)
    {
        Exception exception = 
            t.Exception.InnerExceptions != null && t.Exception.InnerExceptions.Count == 1 
                ? t.Exception.InnerExceptions[0] 
                : t.Exception;

        ExceptionDispatchInfo.Capture(exception).Throw();
    }
}

"You can use the ExceptionDispatchInfo object that is returned by this method at another time and possibly on another thread to rethrow the specified exception, as if the exception had flowed from this point where it was captured to the point where it is rethrown. If the exception is active when it is captured, the current stack trace information and Watson information that is contained in the exception is stored. If it is inactive, that is, if it has not been thrown, it will not have any stack trace information or Watson information."

However, keep in mind that exceptions from async code are generally less meaningful than you would like as all exceptions are thrown from inside the MoveNext method on the state machine generated by the compiler.

like image 119
i3arnon Avatar answered Oct 19 '22 18:10

i3arnon


i3arnon's answer is completely correct, however there's still a few alternatives. The problem you are facing is because throw is the part that captures the stack trace - by throwing the same exception again, you've thrown away the whole stack trace.

The simplest way to avoid that is to let the Task do its job:

t.GetAwaiter().GetResult();

That's it - the exception is rethrown with the correct stack trace and everything. You don't even have to check if the task is faulted - if it is, it will throw, if it isn't, it will not.

Internally, this method uses the ExceptionDispatchInfo.Capture(exception).Throw(); i3arnon has shown, so they are almost equivallent (your code assumes that the task has already completed, faulted or not - if it's not completed yet, IsFaulted will return false).

like image 13
Luaan Avatar answered Oct 19 '22 18:10

Luaan


the above code works really well

I'm not sure why you would want exceptions thrown directly on the thread pool (ContinueWith). This would crash your process without giving any of the code a chance to clean up.

To me, a much more natural approach would be to let the exception bubble up. In addition to allowing natural cleanup, this approach removes all the awkward, weird code:

public static async Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
{
  var chunks = source.Chunk(dop);
  foreach (var chunk in chunks)
    await Task.WhenAll(chunk.Select(s => body(s)));
}

private static IEnumerable<IEnumerable<T>> Chunk<T>(this IEnumerable<T> source, int chunksize)
{
  while (source.Any())
  {
    yield return source.Take(chunksize);
    source = source.Skip(chunksize);
  }
}

// Note: No "ThrowError" at all.
like image 6
Stephen Cleary Avatar answered Oct 19 '22 18:10

Stephen Cleary