使用异步调用时,良好的图案异常处理 [英] Good pattern for exception handling when using async calls

查看:132
本文介绍了使用异步调用时,良好的图案异常处理的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想消费的网络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的应许在太平人寿的斯蒂芬Toub的然后模式。而最接近异步/计谋在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;
}

这篇关于使用异步调用时,良好的图案异常处理的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆