【问题标题】:How to use Task.WhenAny and implement retry如何使用 Task.WhenAny 并实现重试
【发布时间】:2017-10-01 12:25:16
【问题描述】:

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

【问题讨论】:

  • 你能澄清一下你的任务有什么区别吗?目前,您似乎有相同任务的列表。也许您正在向GetSomethingAsync() 传递一些不同的参数?
  • @Sergey,是的,我正在向 GetSomethingAsync 传递一个独特的参数,在重试任务时我需要能够引用它。

标签: c# async-await


【解决方案1】:

我相信在任务中重试 会更容易,然后将 Task.WhenAny-in-a-loop 反模式替换为 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);

【讨论】:

  • 谢谢@Stephen,我会试试的。为此,我曾尝试过使用 Polly,但仍在使用 await Task.WhenAny 模式,这种模式并没有导致任何结果。
  • @macon:不,这很尴尬。总有一个比 Task.WhenAny 在任务完成时删除任务(最终等待它们全部完成)更简洁的解决方案。
【解决方案2】:

如果您出于某种原因不想使用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,那么在五次尝试失败后,将抛出 HttpRequestException 而不是这些结果。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-04
    • 1970-01-01
    • 2022-09-25
    • 1970-01-01
    • 2012-10-25
    • 1970-01-01
    相关资源
    最近更新 更多