同时等待具有独立延续的多个异步调用

在几种情况下,我需要调用多个异步调用(来自相同的事件处理程序),这些异步调用可以彼此独立地进行,每个异步调用都有自己的更新UI的延续。

以下天真实现会导致三个异步操作按顺序执行:

private async void button_Click(object sender, RoutedEventArgs e) { nameTextBox.Text = await GetNameAsync(); cityTextBox.Text = await GetCityAsync(); rankTextBox.Text = await GetRankAsync(); } 

有一个MSDN示例建议将任务的创建与其各自的await语句分开,允许它们并行运行:

 private async void button_Click(object sender, RoutedEventArgs e) { var nameTask = GetNameAsync(); var cityTask = GetCityAsync(); var rankTask = GetRankAsync(); nameTextBox.Text = await nameTask; cityTextBox.Text = await cityTask; rankTextBox.Text = await rankTask; } 

然而,这种方法的局限性在于,任务延续仍然是顺序注册的,这意味着第n 继续在其所有前面的n -1个连续完成之前不能执行,即使它的任务可能是第一个完成的。

使异步任务并行运行的最佳模式是什么,但是一旦各自的任务完成,每个延续都会运行?

编辑 :大多数答案建议等待Task.WhenAll ,如下所示:

 private async void button_Click(object sender, RoutedEventArgs e) { var nameTask = GetNameAsync(); var cityTask = GetCityAsync(); var rankTask = GetRankAsync(); await Task.WhenAll(nameTask, cityTask, rankTask); nameTextBox.Text = nameTask.Result; cityTextBox.Text = cityTask.Result; rankTextBox.Text = rankTask.Result; } 

但是,这不符合我的要求,因为我需要在每个任务完成后立即执行每个继续。 例如,假设GetNameAsync需要5秒, GetCityAsync需要2秒,而GetRankAsync需要8秒。 上面的实现将导致所有三个文本框仅在8秒后更新,即使nameTextBoxcityTextBox的结果早得知。

传统方法是使用ContinueWith为每个异步任务注册相应的延续:

 private async void button_Click(object sender, RoutedEventArgs e) { TaskScheduler uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); await Task.WhenAll( GetNameAsync().ContinueWith(nameTask => { nameTextBox.Text = nameTask.Result; }, uiScheduler), GetCityAsync().ContinueWith(cityTask => { cityTextBox.Text = cityTask.Result; }, uiScheduler), GetRankAsync().ContinueWith(rankTask => { rankTextBox.Text = rankTask.Result; }, uiScheduler)); } 

使用C#5,现在最好使用await-style模式。 通过将每个任务继续对分成自己的方法,可以最容易地实现这一点:

 private async void button_Click(object sender, RoutedEventArgs e) { await Task.WhenAll( PopulateNameAsync(), PopulateCityAsync(), PopulateRankAsync()); } private async Task PopulateNameAsync() { nameTextBox.Text = await GetNameAsync(); } private async Task PopulateCityAsync() { cityTextBox.Text = await GetCityAsync(); } private async Task PopulateRankAsync() { rankTextBox.Text = await GetRankAsync(); } 

定义所有这些琐碎的方法很快就会变得很麻烦,所以可以将它们浓缩成异步lambda:

 private async void button_Click(object sender, RoutedEventArgs e) { await Task.WhenAll( new Func(async () => { nameTextBox.Text = await GetNameAsync(); })(), new Func(async () => { cityTextBox.Text = await GetCityAsync(); })(), new Func(async () => { rankTextBox.Text = await GetRankAsync(); })()); } 

如果经常使用这种模式,那么定义一个可以使用Func lambdas并执行它们的实用程序方法也会很有帮助,这使得我们的事件处理程序代码更简洁和可读:

 public static Task WhenAllTasks(params Func[] taskProducers) { return Task.WhenAll(taskProducers.Select(taskProducer => taskProducer())); } private async void button_Click(object sender, RoutedEventArgs e) { await WhenAllTasks( async () => nameTextBox.Text = await GetNameAsync(), async () => cityTextBox.Text = await GetCityAsync(), async () => rankTextBox.Text = await GetRankAsync()); } 

一个更简单的选择是:

 private async void button_Click(object sender, RoutedEventArgs e) { var results = await Task.WhenAll( GetNameAsync(), GetCityAsync(), GetRankAsync() ); nameTextBox.Text = results[0]; nameCityBox.Text = results[1]; nameRankBox.Text = results[2]; } 

没有关闭,没有额外的状态机。

据我了解,您需要查询三个异步资源,并且只有在返回三个异步资源后才更新UI。 如果这是正确的Task.WhenAll控件是想要使用。 就像是

 private async void button_Click(object sender, RoutedEventArgs e) { string nametext = null; string citytext = null; string ranktext = null; await Task.WhenAll( async () => nametext = await GetNameAsync(), async () => citytext = await GetCityAsync(), async () => ranktext = await GetRankAsync() ); nameTextBox.Text = nametext; nameCityBox.Text = citytext; nameRankBox.Text = ranktext; } 
 private async void button_Click(object sender, RoutedEventArgs) { var nameTask = GetNameAsync(); var cityTask= GetCityAsync(); var rankTask= GetRankAsync(); System.Threading.Tasks.Task.WaitAll(nameTask, cityTask, rankTask); nameTextBox.Text = nameTask.Result; cityTextBox.Text = cityTask.Result; rankTextBox.Text = rankTask.Result; } 

更多细节: https : //msdn.microsoft.com/pt-br/library/dd270695(v = vs.110).aspx