Concurrently awaiting multiple asynchronous calls with independent continuations

前端 未结 4 961
时光说笑
时光说笑 2021-01-06 14:11

There are several scenarios where I need to invoke multiple asynchronous calls (from the same event handler) that can proceed independently of each other, with each one havi

4条回答
  •  天涯浪人
    2021-01-06 14:40

    The traditional approach was to use ContinueWith for registering the respective continuation to each asynchronous task:

    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));
    }
    

    With C# 5, it is now preferable to use an await-style pattern. This may be most easily achieved by splitting up each task–continuation pair into its own method:

    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();
    }
    

    Defining all these trivial methods quickly become cumbersome, so one may condense them into async lambdas instead:

    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(); })());
    }
    

    If this pattern is used frequently, it would also be helpful to define a utility method that can take the Func lambdas and execute them, making our event handler's code more concise and readable:

    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());
    }
    

提交回复
热议问题