我想使用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
。
您是否可以共享有关如何正确使用异步调用并以良好方式处理异常的模式?基本的解释也将受到赞赏。
答案 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
内,您可以使用所有熟悉的同步代码语句,例如GetThreePagesHelper
,using
,for
,while
等。但重要的是要了解这种模式的异步代码流:
try/catch