在 Task.WaitAll 中处理取消的任务和任务异常?

Posted

技术标签:

【中文标题】在 Task.WaitAll 中处理取消的任务和任务异常?【英文标题】:Handle cancelled task and task exceptions in 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() 【参考方案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);

【讨论】:

以上是关于在 Task.WaitAll 中处理取消的任务和任务异常?的主要内容,如果未能解决你的问题,请参考以下文章

HttpClient - 任务被取消?

带有异步 lambda 和 Task.WaitAll 的 Task.Factory.StartNew

C# Task 暂停与取消

Task.WaitAll代替WaitHandle.WaitAll

Task.WaitAll和Task.WaitAny

Task.WhenAll(taskList).Wait() 是不是与 Task.WaitAll(taskList) 相同?