gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-30 14:10:35 27 4
gpt4 key购买 nike

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

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

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

有一个 MSDN example这建议将任务的创建与其各自的 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;
}

然而,这种方法的局限性在于任务延续仍然是按顺序注册的,这意味着 nth 延续不能在其前面的所有 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 风格的模式。这可能最容易通过将每个任务-延续对拆分成它自己的方法来实现:

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<Task>(async () => { nameTextBox.Text = await GetNameAsync(); })(),
new Func<Task>(async () => { cityTextBox.Text = await GetCityAsync(); })(),
new Func<Task>(async () => { rankTextBox.Text = await GetRankAsync(); })());
}

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

public static Task WhenAllTasks(params Func<Task>[] 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());
}

关于c# - 同时等待多个具有独立延续的异步调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23347894/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com