以下代码是否捕获了TPL中原始,继续和子任务的exception?

我正在使用TPL和async / await在我的应用程序的webclient上构建异步API。 很少有地方(通常我需要运行一堆异步任务并最终等待所有这些),我正在关注代码片段。 我只是想确保我正确,因为即使使用TPL编写异步代码相对容易并且async / await 调试/故障排除仍然具有挑战性 (在客户站点上的交互式调试和故障排除问题) – 所以想要正确。

我的目标:能够捕获原始任务,继续任务以及子任务生成的exception,以便我可以处理它(如果需要)。 我不希望任何例外是冷杉和遗忘。

我使用的基本原理: 1.net框架确保将exception附加到任务2.可以将try / catch块应用于async / await以提供同步代码的幻觉/可读性(参考: http://channel9.msdn。 com / Events / TechDays / Techdays-2014-the-Netherlands / Async-programming-deep-dive , http://blogs.msdn.com/b/ericlippert/archive/2010/11/19/asynchrony-in-c- 5-part-seven-exceptions.aspx , http://msdn.microsoft.com/en-us/library/dd537614.aspx等)

问题我想获得批准,我们已经实现了期望的目标(我可以从原始,继续和子任务中捕获exception),并且我可以对样本做任何改进:

例如,是否存在其中一个组合任务(例如,未解包的代理任务)根本不会被激活(waitforactivation状态)的情况,那么waitall可能只是等待任务开始? 我的理解是这些情况永远不会发生,因为延续任务总是执行,并返回由代理使用wnwrap跟踪的任务。 只要我在所有层和apis中遵循类似的模式,模式就应该捕获链接任务中的所有聚合exception。

注意:基本上我正在寻找建议,例如,如果原始任务状态没有运行完成,或者使用附加到父级,那么在继续任务中创建虚拟任务是创建的,这样我只能在父级等上等待才能看到所有可能性以便我可以选择最好的选择,因为这个模式在我的应用程序中严重依赖于error handling。

static void SyncAPIMethod(string[] args) { try { List composedTasks = new List(); //the underlying async method follow the same pattern //either they chain the async tasks or, uses async/await //wherever possible as its easy to read and write the code var task = FooAsync(); composedTasks.Add(task); var taskContinuation = task.ContinueWith(t => { //Intentionally not using TaskContinuationOptions, so that the //continuation task always runs - so that i can capture exception //in case something is wrong in the continuation List childTasks = new List(); if (t.Status == TaskStatus.RanToCompletion) { for (int i = 1; i <= 5; i++) { var childTask = FooAsync(); childTasks.Add(childTask); } } //in case of faulted, it just returns dummy task whose status is set to //'RanToCompletion' Task wa = Task.WhenAll(childTasks); return wa; }); composedTasks.Add(taskContinuation); //the unwrapped task should capture the 'aggregated' exception from childtasks var unwrappedProxyTask = taskContinuation.Unwrap(); composedTasks.Add(unwrappedProxyTask); //waiting on all tasks, so the exception will be thrown if any of the tasks fail Task.WaitAll(composedTasks.ToArray()); } catch (AggregateException ag) { foreach(Exception ex in ag.Flatten().InnerExceptions) { Console.WriteLine(ex); //handle it } } } 

最好的祝福。

来自评论:

IMO,使用async / await,这段代码可以更加简单和优雅。 我不明白为什么你坚持使用ContinueWith和Unwrap,以及为什么你将内部和外部(未包装)任务添加到composedTasks。

我的意思是下面的东西。 我认为它与原始代码的作用相同,但没有使用UnwrapContinueWithUnwrapforms的不必要的冗余。 如果使用async/await几乎不需要那些。

 static void Main(string[] args) { Func doAsync = async () => { await FooAsync().ConfigureAwait(false); List childTasks = new List(); for (int i = 1; i <= 5; i++) { var childTask = FooAsync(); childTasks.Add(childTask); } await Task.WhenAll(childTasks); }; try { doAsync().Wait(); } catch (AggregateException ag) { foreach (Exception ex in ag.Flatten().InnerExceptions) { Console.WriteLine(ex); //handle it } } } static async Task FooAsync() { // simulate some CPU-bound work Thread.Sleep(1000); // we could have avoided blocking like this: // await Task.Run(() => Thread.Sleep(1000)).ConfigureAwait(false); // introduce asynchrony // FooAsync returns an incomplete Task to the caller here await Task.Delay(1000).ConfigureAwait(false); } 

更新以解决评论:

有一些用例我会在“创建子任务”之后继续调用更多“独立”任务。

基本上,任何异步任务工作流程都有三种常见方案:顺序组合,并行组合或这两者的任意组合(混合组合):

  • 顺序组成:

     await task1; await task2; await task3; 
  • 并行组成:

     await Task.WhenAll(task1, task2, task3); // or await Task.WhenAny(task1, task2, task3); 
  • 混合成分:

     var func4 = new Func(async () => { await task2; await task3; }); await Task.WhenAll(task1, func4()); 

如果上述任何任务执行CPU绑定工作,您可以使用Task.Run ,例如:

  var task1 = Task.Run(() => CalcPi(numOfPiDigits)); 

其中CalcPi是一种进行实际计算的同步方法。