我已经创建了一小段代码来并行运行多个异步操作(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;
}
}
就并行运行任务而言,上述代码运行良好。但是,在抛出异常时,我会遇到一些问题。
异常捕获代码在返回异常消息时效果很好,但堆栈跟踪还有很多不足之处 - 因为它指向ThrowError
方法,而不是最初生成的方法例外。我可以按照自己的方式工作并找出附加的调试器出了什么问题,但如果我发布了这个应用程序,我就不会有这个选项 - 充其量,我会在堆栈中遇到异常跟踪记录。
那么 - 在运行异步任务时,有没有办法获得更有意义的堆栈跟踪?
PS。这适用于WindowsRT应用程序,但我认为问题并不仅限于WindowsRT ...
答案 0 :(得分:26)
那么 - 在运行异步任务时,有没有办法获得更有意义的堆栈跟踪?
是的,您可以使用.NET 4.5中引入的ExceptionDispatchInfo.Capture
进行异步等待:
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();
}
}
&#34;您可以在另一个时间使用此方法返回的
ExceptionDispatchInfo
对象,并可能在另一个线程上重新抛出指定的异常,就像异常从捕获它的这一点流出一样到了它被重新抛出的程度。 如果异常在捕获时处于活动状态,则会存储异常中包含的当前堆栈跟踪信息和Watson信息。如果它处于非活动状态,即,如果它没有被抛出,它将没有任何堆栈跟踪信息或Watson信息。&#34;
但是,请记住,异步代码中的异常通常没有您想要的那么有意义,因为所有异常都是从编译器生成的状态机上的MoveNext
方法内抛出的。
答案 1 :(得分:12)
i3arnon的回答是完全正确的,但仍有一些选择。您面临的问题是因为throw
是捕获堆栈跟踪的部分 - 通过再次抛出相同的异常,您将丢弃整个堆栈跟踪。
避免这种情况的最简单方法是让Task
完成工作:
t.GetAwaiter().GetResult();
它就是 - 使用正确的堆栈跟踪和所有内容重新抛出异常。您甚至不必检查任务是否出现故障 - 如果是,它将抛出,如果不是,则不会。
在内部,此方法使用ExceptionDispatchInfo.Capture(exception).Throw();
i3arnon已显示,因此它们几乎等效(您的代码假定任务已经完成,出现故障或不出现 - 如果它&#39;尚未完成,IsFaulted
将返回false)。
答案 2 :(得分:5)
上面的代码效果很好
我不确定为什么要直接在线程池(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.