我有一个创建多个基于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而不是允许我检查任务状态。在异常处理程序中,我不再有任何方法可以重试失败的任务。
答案 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问题中找到解决方案。