使用 Task.WhenAny 和 Task.WhenAll

在这篇文章中,我们将看看何时应该使用Task.WhenAnyand Task.WhenAll。我还将解释 和 之间的Task.WaitAny区别Task.WaitAll。然后我继续展示这些结构的好坏用法的一些例子。我们还将看到一些情况,例如当我们需要立即处理和解决它时。

什么时候应该使用 Task.WhenAny

我们Task.WhenAny在有一组任务时使用,但我们只对第一个完成的任务感兴趣。例如,当我们有几个异步 API,它们都做同样的事情时,就会发生这种情况。但是我们希望从第一个返回结果的人那里接收结果。

private static async Task<string> FirstRespondingUrlAsync(string urlA, string urlB)
{
// Start both downloads concurrently.
Task<string> downloadTaskA = _httpClient.GetHtmlResponseAsync(urlA);
Task<string> downloadTaskB = _httpClient.GetHtmlResponseAsync(urlB);
// Wait for either of the tasks to complete.
Task<string> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB);
// Return the length of the data retrieved from that URL.
string data = await completedTask;
return data;
}
view rawWhenAny.cs hosted with  by GitHub

另一种情况是当我们需要备份机制时。因此,如果其中一项任务失败,另一项任务完成并给我们结果。出于这个原因,当我们收到一个已完成的任务等待时,如果发生任何异常,它不会传播到返回的任务。请记住,当我们在第 4 行通过调用调用该方法时GetHtmlResponseAsync,该方法正在获取结果的途中。所以WhenAny 在这种情况下,返回完成的第一个任务。最后通过等待返回的任务,我们得到了实际的结果。

另一个需要注意的重要事情是,当第一个任务完成并返回时,其他任务仍在运行。如果我们不取消它们,这里会发生什么,它们会运行到完成并被放弃。所以在这些情况下,当我们不需要其他任务时,我们可以取消它们。

private async Task<HttpResponseMessage> FirstRespondingUrlWithCancellationAsync(string urlA, string urlB, CancellationToken ct)
{
// Start both downloads concurrently.
Task<HttpResponseMessage> downloadTaskA = _httpClient.GetAsync(urlA, ct);
Task<HttpResponseMessage> downloadTaskB = _httpClient.GetAsync(urlB, ct);
// Wait for either of the tasks to complete.
Task<HttpResponseMessage> completedTask = await Task.WhenAny(downloadTaskA, downloadTaskB);
// Return the length of the data retrieved from that URL.
HttpResponseMessage data = await completedTask;
return data;
}
async Task UseFirstRespondingUrlWithCancellationAsync()
{
var cts = new CancellationTokenSource();
var result = await FirstRespondingUrlWithCancellationAsync("url1", "url2", cts.Token);
//Now we can cancel the rest of the tasks since we already got the result we need
cts.Cancel();
}

我们在这里所做的是将取消标记作为参数并将它们传递给GetAsync. 所以现在你可以在UseFirstRespondingUrlWithCancellationAsync 方法中看到,当我们得到需要的结果时,我们可以取消剩下的任务。

什么时候应该使用 Task.WhenAll

我们可以使用Task.WhenAll来等待一组任务完成。我们也可以循环等待每个任务。但这将是低效的,因为我们一次分派任务。

public static async Task DownLoadAsync(params string[] downloads)
{
var client = new HttpClient();
foreach (var uri in downloads)
{
string content = await client.GetStringAsync(uri);
UpdateUI(content);
}
}
private static void UpdateUI(string content)
{
throw new NotImplementedException();
}

正如您在上面的方法中看到的那样,我们GetStringAsync 当时调用方法一。我们想要的是调度同时获取字符串的任务。在这些情况下,我们可以使用Task.WhenAll. 所以上面的代码可以这样重构。

public static async Task DownLoadAsync2(params string[] downloads)
{
var tasks = new List<Task<string>>();
foreach (var uri in downloads)
{
var client = new HttpClient();
tasks.Add(client .GetStringAsync(uri));
}
await Task.WhenAll(tasks);
tasks.ForEach(t => UpdateUI(t.Result));
}

在上面的代码中,我们将任务聚合到一个列表中并异步等待所有任务。另一个重要的事情是如何Task.WhenAll处理异常。当抛出一个或多个异常时,异常将作为AggregateException. 如果我们在这种情况下使用 try catch,我们只会从异常集合中获取第一个异常。如果我们需要所有这些,我们不能等待任务并在同一行获得结果。 

private static Task ThrowInvalidOperationExceptionAsync() => throw new NotImplementedException();
private static Task ThrowNotImplementedExceptionAsync() => throw new InvalidOperationException();
static async Task AllExceptionsAsync()
{
var task1 = ThrowNotImplementedExceptionAsync();
var task2 = ThrowInvalidOperationExceptionAsync();
Task allTasks = Task.WhenAll(task1, task2);
try
{
await allTasks;
}
catch
{
AggregateException allExceptions = allTasks.Exception;
}
}

在上面的代码摘录中,我们将任务分配给一个变量。然后我们等待它,然后使用之前的任务变量来获取所有异常。

处理完成的任务

另一种可能偶尔出现的情况是,我们需要在任务完成后立即对其进行一些处理。因为现在我们使用的方式Task.WhenAll,我们一次得到所有任务的结果。在启动下一个之前,我们没有机会对结果进行任何更改。最好的解决方案是创建一个中间方法,为我们执行等待和处理,并改为等待该方法。

static async Task<int> ExampleTaskAsync(int val)
{
await Task.Delay(TimeSpan.FromSeconds(val));
return val;
}
static async Task AwaitAndProcessAsync(Task<int> task)
{
var result = await task;
Trace.WriteLine(result);
}
static async Task ProcessTasksAsync()
{
Task<int> task1 = ExampleTaskAsync(2);
Task<int> task2 = ExampleTaskAsync(3);
Task<int> task3 = ExampleTaskAsync(1);
var tasks = new[] { task1, task2, task3 };
var processingTasks = tasks.Select(AwaitAndProcessAsync).ToList();
await Task.WhenAll(processingTasks);
}

正如您在上面的代码中看到的,AwaitAndProcessAsync 方法是为我们执行等待和额外处理的方法。我们首先创建任务,然后通过AwaitAndProcessAsync 方法对它们进行管道传输并获取任务列表。然后,我们使用Task.WhenAll等待所有这些任务,并在它们返回值后立即处理它们。Stephen Toub 的一篇文章进行了更详细的介绍

Task.WhenAny 与 Task.WaitAny

这两者之间的主要区别在于Task.WaitAny阻塞操作。它与使用task.Wait()or相同task.Result。不同之处在于它需要多个任务并从第一个完成的任务中返回结果。Task.WaitAny在某些情况下可以使用,但很少见。也许当我们想要阻止操作时,例如在控制台应用程序中。但即使这样也是不能接受的。因为从 C# 7.1 开始,我们可以使用async Task Main(string[] args).

Task.WhenAll 与 Task.WaitAll

Like Task.WaitAny,Task.WaitAll也是阻塞操作。它的行为与 相同task.Wait(),只是它需要一组任务并等待所有任务完成。就像它的对应部分一样,Task.WaitAll如果有的话,也很少使用。Task.WhenAll在大多数情况下,我们应该使用非阻塞。

概括

在这篇文章中,我解释了何时以及如何使用Task.WhenAnyand Task.WhenAll。我们还看到了它们与方法的差异Task.WaitAny以及Task.WaitAll有关使用它们的一些最佳实践。我还简要介绍了使用 Task.WhenAll 时的情况和解决方案,我们需要在任务返回值后立即进行一些处理。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值