问题描述
我想消费的网络API,我看到很多人推荐 System.Net.Http.HttpClient
。
I want to consume an Web API and I see many people recommending System.Net.Http.HttpClient
.
这很好......但我只有VS-2010,所以我不能使用异步/计谋
只是还没有。相反,我想我可以使用任务<结合
到 ContinueWith
; TResult&GT。所以我想这块code:
That's fine... but I have only VS-2010, so I cannot use async/await
just yet. Instead, I guess I could use Task<TResult>
in combination to 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);
});
我的第一个看法是要认识到,如果URL不可用不产生任何错误,但也许会有更多这样的错误...
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...
所以,我试图找到一种方法来处理这种异步调用异常(尤其是对HttpClient的)。我注意到,任务具有 IsFaulted
属性和 AggregateException
这也许可以用,但我还不确定怎么样。
So I am trying to find a way to handle exceptions for such async calls (particularly for HttpClient). I noticed that "Task" has IsFaulted
property and an AggregateException
which maybe could be used, but I am not sure yet how.
另一种看法是, GetStringAsync
返回任务&LT;字符串&GT;
,而 GetAsync
返回任务&LT; Htt的presponseMessage&GT;
。后者可能是更可能有用,因为它presents一个状态code
。
Another observation was that GetStringAsync
returns Task<string>
, but GetAsync
returns Task<HttpResponseMessage>
. The latter could be maybe more useful, since it presents a StatusCode
.
你能分享如何使用异步调用正确和处理异常的好办法的模式?基本解释是pciated以及AP $ P $。
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.
推荐答案
我不会用一个单独的 ContinueWith
延续成功和故障场景。我宁愿在一个地方处理这两种情况下,使用的try / catch
:
I would not use a separate ContinueWith
continuation for successful and faulted scenarios. I'd rather handle both cases in a single place, using 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
是一个非通用工作
(而不是任务&LT; TResult&GT ;
),你可以做 t.GetAwaiter()调用getResult()
里面重新抛出原始异常的。 ContinueWith
拉姆达; t.Wait()
将工作太。要ppared处理 AggregatedException
$ P $,你可以得到内部异常的东西是这样的:
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.Wait()
would work too. Be prepared to handle AggregatedException
, you can get to the inner exception with something like this:
catch (Exception ex)
{
while (ex is AggregatedException && ex.InnerException != null)
ex = ex.InnerException;
MessageBox.Show(ex.Message);
}
如果你处理了一系列 ContinueWith
,平时你不用来处理内部的每个的 ContinueWith 。做一次最外层产生的任务,例如:
If you're dealing with a series of ContinueWith
, usually you don't have to handle exceptions inside each ContinueWith
. Do it once for the outermost resulting task, e.g.:
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
拉姆达为你所访问的内部任务的结果( taskN.Result
)。
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
).
这code是功能性的,但它也是丑陋的,无法读取。 JavaScript开发人员称之为的末日回调金字塔的。他们有诺言来对付它。 C#开发人员有异步/计谋
,你是不幸的是无法使用的,因为在VS2010的限制。
This code is functional, but it's also ugly and non-readable. JavaScript developers call it The Callback Pyramid of 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.
IMO,最接近JavaScript的应许在太平人寿的。而最接近异步/计谋
在C#4.0是他的迭代
从同一个博客文章,它使用模式C#收益率
功能。
IMO, the closest thing to the JavaScript Promises in TPL is Stephen Toub's Then
pattern. 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.
使用迭代
图案,上面的code可以改写以更可读的方式。请注意,在 GetThreePagesHelper
您可以使用所有熟悉的同步code语句像使用
,为
,,而
,的try / catch
等,然而重要的是要了解异步code这种模式流量:
Using the Iterate
pattern, the above code could be rewritten in a more readable way. 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:
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;
}
这篇关于使用异步调用时,良好的图案异常处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!