【问题标题】:Handle cancelled task and task exceptions in Task.WaitAll?在 Task.WaitAll 中处理取消的任务和任务异常?
【发布时间】:2014-10-10 22:53:24
【问题描述】:

我正在使用 TPL 抓取一组 Url,然后进行一些处理。

for (int i = 0; i < list.Count; i++)
{
    var tuple = list[i];
    string url = tuple.Item2;

    tasks[i] = httpClient.GetStringAsync(url).
        ContinueWith(task => {
        {
            ......

        });
}
Task.WaitAll(tasks);

问题在于,在Task.WaitAll 语句中,它似乎经常会抛出异常,因为任务已被取消。我知道httpClient.GetStringAsync 可能并不总是确保成功,所以我想在发生异常时在httpClient.GetStringAsync 中添加重试逻辑。这样做的正确方法是什么?

【问题讨论】:

  • 如果重试失败,还想要异常吗?
  • @I3arnon:我愿意。但随后异常将作为 AggregateException 传递给 Task.WaitAll。对吗?
  • 是的,因为一个任务可能包含多个异常。如果你想要为async-await 构建的await Task.WhenAll,那么你会在里面得到实际的异常(第一个是)。
  • @I3arnon:问题是我在 Main() 中使用了这个函数,所以我不能使用 await Task.WhenAll..
  • 如果这很关键,您可以使用Task.WhenAll(...).GetAwaiter().GetResult()

标签: c# asynchronous task-parallel-library async-await dotnet-httpclient


【解决方案1】:

您可以使用for 循环轻松地围绕GetStringAsync 进行重试,直到没有异常或达到重试限制。我使用await 存储任务并从中提取结果,因此如果达到重试限制但未成功,则会重新抛出异常:

async Task<string> GetStringAsync(HttpClient client,string url, int retries)
{
    Task<string> task = null;
    for (int i = 0; i < retries; i++)
    {
        try
        {
            task = client.GetStringAsync(url);
            await task;
            break;
        }
        catch
        {
            // log
        }
    }

    return await task;
}

您甚至可以将其作为HttpClient 上的扩展方法:

static async Task<string> GetStringAsync(this HttpClient client, string url, int retries);

【讨论】:

    【解决方案2】:

    如果您不想使用async/await,可以使用以下扩展方法作为起点。

    static class HttpClientExtentions
    {
        public static Task<string> GetStringWithRetryAsync(this HttpClient client, string url, int retries)
        {
            var completion = new TaskCompletionSource<string>();
            var ex = new List<Exception>();
    
            Task<string> getString = client.GetStringAsync(url);
            Action<Task<string>> continueAction = null;
            continueAction = (t) =>
            {
                if (t.IsFaulted)
                {
                    ex.Add(t.Exception);
    
                    if (retries-- > 0)
                    {
                        getString = client.GetStringAsync(url);
                        getString.ContinueWith(continueAction);
                    }
                    else
                    {
                        completion.SetException(new AggregateException(ex));
                    }
                }
                else // assume success, you could also handle cancellation
                {
                    completion.SetResult(t.Result);
                }
    
            };
    
            getString.ContinueWith(continueAction);
    
            return completion.Task;
        }
    }
    

    这样使用:

    for (int i = 0; i < list.Count; i++)
    {
        var tuple = list[i];
        string url = tuple.Item2;
    
        int retryCount = 3;
        var httpClient = new HttpClient(); // should create new object for each req
        tasks[i] = httpClient.GetStringWithRetryAsync(url, retryCount).
            ContinueWith(task => {
            {
                //......
    
            });
    }
    Task.WaitAll(tasks);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-26
      • 2013-11-19
      • 1970-01-01
      • 2014-03-06
      • 1970-01-01
      相关资源
      最近更新 更多