我已经创建了一小段代码来并行运行多个异步操作(Parallel该类本身不适合异步操作).
它看起来像这样:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
就并行运行任务而言,上述代码运行良好.但是,在抛出异常时,我会遇到一些问题.
异常捕获代码在返回异常消息时效果很好,但是堆栈跟踪有很多不足之处 - 因为它指向ThrowError方法,而不是最初生成异常的方法.我可以按照自己的方式工作并找出附加的调试器出了什么问题,但是如果我发布了这个应用程序,我就不会有这个选项 - 充其量,我会记录堆栈跟踪的异常.
那么 - 在运行异步任务时,有没有办法获得更有意义的堆栈跟踪?
PS.这适用于WindowsRT应用程序,但我认为问题不仅限于WindowsRT ......
i3a*_*non 26
那么 - 在运行异步任务时,有没有办法获得更有意义的堆栈跟踪?
是的,您可以使用ExceptionDispatchInfo.Capture.NET 4.5中引入的async-await特别注意:
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();
}
}
Run Code Online (Sandbox Code Playgroud)
"您可以
ExceptionDispatchInfo在另一个时间使用此方法返回的对象,并且可能在另一个线程上使用该对象来重新抛出指定的异常,就好像异常从捕获它的那一点流到重新抛出的点.如果异常在捕获时,它是活动的,存储异常中包含的当前堆栈跟踪信息和Watson信息.如果它是非活动的,即,如果它没有被抛出,它将没有任何堆栈跟踪信息或Watson信息".
但是,请记住,异步代码中的异常通常没有您想要的那么有意义,因为所有异常都是从MoveNext编译器生成的状态机上的方法内部抛出的.
Lua*_*aan 12
i3arnon的答案是完全正确的,但仍然有一些选择.您面临的问题是因为throw是捕获堆栈跟踪的部分 - 通过再次抛出相同的异常,您抛弃了整个堆栈跟踪.
避免这种情况的最简单方法是让它Task完成它的工作:
t.GetAwaiter().GetResult();
Run Code Online (Sandbox Code Playgroud)
就是这样 - 正确的堆栈跟踪和所有内容都会重新抛出异常.您甚至不必检查任务是否出现故障 - 如果是,它将抛出,如果不是,则不会抛出.
在内部,这个方法使用ExceptionDispatchInfo.Capture(exception).Throw();i3arnon已经显示,所以它们几乎是等价的(你的代码假设任务已经完成,出现故障或者没有 - 如果它还没有完成,IsFaulted将返回false).
上面的代码工作得很好
我不确定为什么你会希望直接在线程池(ContinueWith)上抛出异常.这会使您的进程崩溃,而不会给任何代码提供清理的机会.
对我来说,更自然的方法是让异常泡沫化.除了允许自然清理之外,这种方法还删除了所有尴尬,奇怪的代码:
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.
Run Code Online (Sandbox Code Playgroud)