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...
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.
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.
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.
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.
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:
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.
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.
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.
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.
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).
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With