[英]Good pattern for exception handling when using async calls
I want to consume an Web API and I see many people recommending System.Net.Http.HttpClient
. 我想使用Web API,我看到很多人推荐
System.Net.Http.HttpClient
。
That's fine... but I have only VS-2010, so I cannot use async/await
just yet. 那很好......但我只有VS-2010,所以我还不能使用
async/await
。 Instead, I guess I could use Task<TResult>
in combination to ContinueWith
. 相反,我想我可以将
Task<TResult>
组合使用到ContinueWith
。 So I tried this piece of code: 所以我尝试了这段代码:
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);
});
My first observation was to realize that it doesn't generate any error if URL is not available, but maybe there will be more errors like this... 我的第一个观察是认识到如果URL不可用它不会产生任何错误,但可能会有更多这样的错误......
So I am trying to find a way to handle exceptions for such async calls (particularly for HttpClient). 所以我试图找到一种方法来处理这种异步调用的异常(特别是对于HttpClient)。 I noticed that "Task" has
IsFaulted
property and an AggregateException
which maybe could be used, but I am not sure yet how. 我注意到“Task”有
IsFaulted
属性和AggregateException
可能会被使用,但我不确定如何。
Another observation was that GetStringAsync
returns Task<string>
, but GetAsync
returns Task<HttpResponseMessage>
. 另一个观察是
GetStringAsync
返回Task<string>
,但GetAsync
返回Task<HttpResponseMessage>
。 The latter could be maybe more useful, since it presents a StatusCode
. 后者可能更有用,因为它提供了一个
StatusCode
。
Could you share a pattern on how to use the async calls correctly and handle exceptions in a good way? 你能否共享一个关于如何正确使用异步调用并以良好方式处理异常的模式? Basic explanation would be appreciated as well.
基本的解释也将受到赞赏。
I would not use a separate ContinueWith
continuation for successful and faulted scenarios. 我不会为成功和故障情况使用单独的
ContinueWith
延续。 I'd rather handle both cases in a single place, using try/catch
: 我宁愿使用
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()
);
If t
is a non-generic Task
(rather than a Task<TResult>
), you can do t.GetAwaiter().GetResult()
to re-throw the original exception inside the ContinueWith
lambda; 如果
t
是非泛型Task
(而不是Task<TResult>
),则可以执行t.GetAwaiter().GetResult()
在ContinueWith
lambda中重新抛出原始异常; t.Wait()
would work too. t.Wait()
也会起作用。 Be prepared to handle AggregatedException
, you can get to the inner exception with something like this: 准备好处理
AggregatedException
,您可以使用以下内容获取内部异常:
catch (Exception ex)
{
while (ex is AggregatedException && ex.InnerException != null)
ex = ex.InnerException;
MessageBox.Show(ex.Message);
}
If you're dealing with a series of ContinueWith
, usually you don't have to handle exceptions inside each ContinueWith
. 如果您正在处理一系列的
ContinueWith
,通常您不必处理每个 ContinueWith
异常。 Do it once for the outermost resulting task, eg: 为最外面的结果任务执行一次,例如:
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());
}
Any exceptions thrown inside inner tasks will propagate to the outermost ContinueWith
lambda as you're accessing the results of the inner tasks ( taskN.Result
). 当你访问内部任务的结果(
taskN.Result
)时,内部任务中抛出的任何异常都会传播到最外面的ContinueWith
lambda。
This code is functional, but it's also ugly and non-readable. 这段代码很实用,但它也很丑陋且不可读。 JavaScript developers call it The Callback Pyramid of Doom .
JavaScript开发人员称之为Doom的回调金字塔 。 They have Promises to deal with it.
他们有承诺来处理它。 C# developers have
async/await
, which you're unfortunately not able to use because of the VS2010 restrain. C#开发人员有
async/await
,由于VS2010的限制,你很遗憾无法使用它。
IMO, the closest thing to the JavaScript Promises in TPL is Stephen Toub's Then
pattern . IMO,与TPL中的JavaScript Promises最接近的是Stephen Toub的
Then
模式 。 And the closest thing to async/await
in C# 4.0 is his Iterate
pattern from the same blog post, which uses the C# yield
feature. 与C#4.0中
async/await
最接近的是来自同一博客文章的Iterate
模式,它使用了C# yield
功能。
Using the Iterate
pattern, the above code could be rewritten in a more readable way. 使用
Iterate
模式,可以以更易读的方式重写上述代码。 Note that inside GetThreePagesHelper
you can use all the familiar synchronous code statements like using
, for
, while
, try/catch
etc. It is however important to understand the asynchronous code flow of this pattern: 请注意,在
GetThreePagesHelper
您可以使用所有熟悉的同步代码语句,例如using
, for
, while
, try/catch
等。但是,了解此模式的异步代码流非常重要:
void GetThreePagesV2()
{
Iterate(GetThreePagesHelper()).ContinueWith((iteratorTask) =>
{
try
{
var lastTask = (Task<string>)iteratorTask.Result;
var result = lastTask.Result;
MessageBox.Show(result);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
throw;
}
},
CancellationToken.None,
TaskContinuationOptions.None,
TaskScheduler.FromCurrentSynchronizationContext());
}
IEnumerable<Task> 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<string>();
resultTcs.SetResult(page1 + page1 + page3);
yield return resultTcs.Task;
}
}
/// <summary>
/// A slightly modified version of Iterate from
/// http://blogs.msdn.com/b/pfxteam/archive/2010/11/21/10094564.aspx
/// </summary>
public static Task<Task> Iterate(IEnumerable<Task> asyncIterator)
{
if (asyncIterator == null)
throw new ArgumentNullException("asyncIterator");
var enumerator = asyncIterator.GetEnumerator();
if (enumerator == null)
throw new InvalidOperationException("asyncIterator.GetEnumerator");
var tcs = new TaskCompletionSource<Task>();
Action<Task> 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;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.