如何使用Task.WhenAny并实现重试

时间:2017-05-03 15:14:42

标签: c# async-await

我有一个创建多个基于I / O的任务的解决方案,我使用Task.WhenAny()来管理这些任务。但是,由于网络问题或请求限制等原因,许多任务通常会失败。

我似乎无法找到一种解决方案,使我能够在使用Task.WhenAny()方法时成功重试失败的任务。

以下是我正在做的事情:

var tasks = new List<Task<MyType>>();
foreach(var item in someCollection)
{
   task.Add(GetSomethingAsync());
}
while (tasks.Count > 0)
{
   var child = await Task.WhenAny(tasks);
   tasks.Remove(child);
   ???
}

因此上述结构适用于完成任务,但我还没有找到办法处理和重试失败的任务。 await Task.WhenAny抛出AggregateException而不是允许我检查任务状态。在异常处理程序中,我不再有任何方法可以重试失败的任务。

2 个答案:

答案 0 :(得分:3)

我相信在任务中重试会更容易,然后用Task.WhenAny替换Task.WhenAll - 循环反模式

,例如,使用Polly

var tasks = new List<Task<MyType>>();
var policy = ...; // See Polly documentation
foreach(var item in someCollection)
  tasks.Add(policy.ExecuteAsync(() => GetSomethingAsync()));
await Task.WhenAll(tasks);

或者,更简洁:

var policy = ...; // See Polly documentation
var tasks = someCollection.Select(item => policy.ExecuteAsync(() => GetSomethingAsync()));
await Task.WhenAll(tasks);

答案 1 :(得分:0)

如果由于某种原因不想使用Polly库,可以使用下面的Retry方法。它接受任务工厂,并继续创建然后等待任务,直到成功完成任务或达到maxAttempts

public static async Task<TResult> Retry<TResult>(Func<Task<TResult>> taskFactory,
    int maxAttempts)
{
    int failedAttempts = 0;
    while (true)
    {
        try
        {
            var task = taskFactory();
            return await task.ConfigureAwait(false);
        }
        catch
        {
            failedAttempts++;
            if (failedAttempts >= maxAttempts) throw;
        }
    }
}

然后您可以使用此方法下载(例如)某些网页。

string[] urls =
{
    "https://stackoverflow.com",
    "https://superuser.com",
    //"https://no-such.url",
};
var httpClient = new HttpClient();
var tasks = urls.Select(url => Retry(async () =>
{
    return (Url: url, Html: await httpClient.GetStringAsync(url));
}, maxAttempts: 5));

var results = await Task.WhenAll(tasks);
foreach (var result in results)
{
    Console.WriteLine($"Url: {result.Url}, {result.Html.Length:#,0} chars");
}

输出:

网址:https://stackoverflow.com,112,276个字符
网址:https://superuser.com,122,784个字符

如果取消注释第三个URL,则在尝试5次失败之后,将抛出HttpRequestException而不是这些结果。

Task.WhenAll方法将在传播错误之前等待所有任务的完成。如果最好尽快报告错误,可以在this问题中找到解决方案。