在task.WaitAll中处理已取消的任务和任务异常



我使用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中添加一个重试逻辑。这样做的正确方法是什么?

您可以使用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);

如果您不想使用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);

最新更新