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

时间:2014-03-08 20:51:58

标签: c# .net-4.0 exception-handling task-parallel-library dotnet-httpclient

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

那很好......但我只有VS-2010,所以我还不能使用async/await。相反,我想我可以将Task<TResult>组合使用到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<string>>(usersResultString);
});

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

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

另一个观察结果是GetStringAsync返回Task<string>,但GetAsync返回Task<HttpResponseMessage>。后者可能更有用,因为它呈现StatusCode

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

1 个答案:

答案 0 :(得分:3)

对于成功和出错的情况,我不会使用单独的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<List<string>>(result);
      }
      catch (Exception ex)
      {
          MessageBox.Show(ex.Message);
          lbUsers.Clear();
          lbUsers.Items.Add("Error loading users!");
      }
   }, 
   CancellationToken.None, 
   TaskContinuationOptions.None, 
   TaskScheduler.FromCurrentSynchronizationContext()
);

如果t是非通用的Task(而不是Task<TResult>),您可以t.GetAwaiter().GetResult()将{0}内的原始异常重新抛出兰达ContinueWith也可以。准备好处理t.Wait(),您可以使用以下内容获取内部异常:

AggregatedException

如果您正在处理一系列catch (Exception ex) { while (ex is AggregatedException && ex.InnerException != null) ex = ex.InnerException; MessageBox.Show(ex.Message); } ,通常您不必处理每个 ContinueWith内的异常。对最外面的结果任务执行一次,例如:

ContinueWith

当你访问内部任务(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()); } )的结果时,内部任务中抛出的任何异常都会传播到最外面的ContinueWith lambda。

此代码功能齐全,但它也很难看且不可读。 JavaScript开发人员称之为 Doom的回调金字塔。他们有Promises来处理它。 C#开发人员有taskN.Result,由于VS2010的限制,你很遗憾无法使用它。

IMO,与TPL中的JavaScript Promises最接近的是Stephen Toub's Then pattern。与C#4.0中async/await最接近的是来自同一博文的async/await模式,它使用了C#Iterate功能。

使用yield模式,可以用更易读的方式重写上述代码。请注意,在Iterate内,您可以使用所有熟悉的同步代码语句,例如GetThreePagesHelperusingforwhile等。但重要的是要了解这种模式的异步代码流:

try/catch