使用异步调用时exception处理的良好模式

我想使用Web API,我看到很多人推荐System.Net.Http.HttpClient

那很好……但我只有VS-2010,所以我还不能使用async/await 。 相反,我想我可以将Task组合使用到ContinueWith 。 所以我尝试了这段代码:

 var client = new HttpClient(); client.DefaultRequestHeaders.Accept.Add( new MediaTypeWithQualityHeaderValue("application/json")); client.GetStringAsync(STR_URL_SERVER_API_USERS).ContinueWith(task => { var usersResultString = task.Result; lbUsers.DataSource = JsonConvert.DeserializeObject<List>(usersResultString); }); 

我的第一个观察是认识到如果URL不可用它不会产生任何错误,但可能会有更多这样的错误……

所以我试图找到一种方法来处理这种异步调用的exception(特别是对于HttpClient)。 我注意到“任务”有IsFaulted属性和AggregateException可能会被使用,但我不确定如何。

另一个观察是GetStringAsync返回Task ,但GetAsync返回Task 。 后者可能更有用,因为它提供了一个StatusCode

您能否共享一个关于如何正确使用异步调用并以良好方式处理exception的模式? 基本的解释也将受到赞赏。

我不会为成功和故障情况使用单独的ContinueWith延续。 我宁愿使用try/catch在一个地方处理这两种情况:

 task.ContinueWith(t => { try { // this would re-throw an exception from task, if any var result = t.Result; // process result lbUsers.DataSource = JsonConvert.DeserializeObject>(result); } catch (Exception ex) { MessageBox.Show(ex.Message); lbUsers.Clear(); lbUsers.Items.Add("Error loading users!"); } }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext() ); 

如果t是非genericsTask (而不是Task ),则可以执行t.GetAwaiter().GetResult()ContinueWith lambda中重新抛出原始exception; t.Wait()也会起作用。 准备好处理AggregatedException ,您可以使用以下内容获取内部exception:

 catch (Exception ex) { while (ex is AggregatedException && ex.InnerException != null) ex = ex.InnerException; MessageBox.Show(ex.Message); } 

如果您正在处理一系列的ContinueWith ,通常您不必处理每个 ContinueWithexception。 为最外面的结果任务执行一次,例如:

 void GetThreePagesV1() { var httpClient = new HttpClient(); var finalTask = httpClient.GetStringAsync("http://example.com") .ContinueWith((task1) => { var page1 = task1.Result; return httpClient.GetStringAsync("http://example.net") .ContinueWith((task2) => { var page2 = task2.Result; return httpClient.GetStringAsync("http://example.org") .ContinueWith((task3) => { var page3 = task3.Result; return page1 + page2 + page3; }, TaskContinuationOptions.ExecuteSynchronously); }, TaskContinuationOptions.ExecuteSynchronously).Unwrap(); }, TaskContinuationOptions.ExecuteSynchronously).Unwrap() .ContinueWith((resultTask) => { httpClient.Dispose(); string result = resultTask.Result; try { MessageBox.Show(result); } catch (Exception ex) { MessageBox.Show(ex.Message); } }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); } 

当你访问内部任务的结果( taskN.Result )时,内部任务中抛出的任何exception都会传播到最外面的ContinueWith lambda。

这段代码很实用,但它也很丑陋且不可读。 JavaScript开发人员将其称为Doom的回调金字塔 。 他们有承诺来处理它。 C#开发人员有async/await ,由于VS2010的限制,你很遗憾无法使用它。

IMO,与TPL中的JavaScript Promises最接近的是Stephen Toub的Then模式 。 与C#4.0中async/await最接近的是来自同一博客post的Iterate模式,它使用了C# yieldfunction。

使用Iterate模式,可以以更易读的方式重写上述代码。 请注意,在GetThreePagesHelper您可以使用所有熟悉的同步代码语句,例如usingforwhiletry/catch等。但是,了解此模式的异步代码流非常重要:

 void GetThreePagesV2() { Iterate(GetThreePagesHelper()).ContinueWith((iteratorTask) => { try { var lastTask = (Task)iteratorTask.Result; var result = lastTask.Result; MessageBox.Show(result); } catch (Exception ex) { MessageBox.Show(ex.Message); throw; } }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext()); } IEnumerable GetThreePagesHelper() { // now you can use "foreach", "using" etc using (var httpClient = new HttpClient()) { var task1 = httpClient.GetStringAsync("http://example.com"); yield return task1; var page1 = task1.Result; var task2 = httpClient.GetStringAsync("http://example.net"); yield return task2; var page2 = task2.Result; var task3 = httpClient.GetStringAsync("http://example.org"); yield return task3; var page3 = task3.Result; yield return Task.Delay(1000); var resultTcs = new TaskCompletionSource(); resultTcs.SetResult(page1 + page1 + page3); yield return resultTcs.Task; } } ///  /// A slightly modified version of Iterate from /// http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx ///  public static Task Iterate(IEnumerable asyncIterator) { if (asyncIterator == null) throw new ArgumentNullException("asyncIterator"); var enumerator = asyncIterator.GetEnumerator(); if (enumerator == null) throw new InvalidOperationException("asyncIterator.GetEnumerator"); var tcs = new TaskCompletionSource(); Action nextStep = null; nextStep = (previousTask) => { if (previousTask != null && previousTask.Exception != null) tcs.SetException(previousTask.Exception); if (enumerator.MoveNext()) { enumerator.Current.ContinueWith(nextStep, TaskContinuationOptions.ExecuteSynchronously); } else { tcs.SetResult(previousTask); } }; nextStep(null); return tcs.Task; }