【问题标题】:3 parallel tasks each awaiting their own result3 个并行任务,每个任务都在等待自己的结果
【发布时间】:2014-12-08 21:20:57
【问题描述】:

我想尝试使用后台工作人员。我对使用async/await 很感兴趣。 我有 3 个并行任务。

private async void RunDownloadsAsync()
{
     Task taskDelay1 = Task.Run(() => Task.Delay(10000));
     Task taskDelay2 = Task.Run(() => Task.Delay(15000)); 
     Task taskDelay3 = Task.Run(() => Task.Delay(20000)); 
     await ???
}

假设这些任务中的每一个在完成后都会更改表单上 3 个标签(label1label2label3)的值。即所有标签都设置为“待处理”,当每个任务完成后,其对应的标签值将变为“已完成”。

我想await每个任务相应地让他们每个人都做自己相应的任务。这意味着如果taskDelay1 的工作已完成,则将label1 的文本设置为“已完成”,而taskDelay2taskDelay3 仍处于待处理状态。但是如果我把 3 awaits 放在那里,程序将等待它们全部完成任务,然后继续执行其余代码。

我怎样才能继续剩下的代码,其中每个标签只等待自己的任务完成,然后决定做什么?

【问题讨论】:

    标签: c# asynchronous async-await


    【解决方案1】:

    您应该使用Task.WhenAll方法等待所有这些和ContinueWith在任务完成时执行操作。

     Task taskDelay1 = Task.Run(() => Task.Delay(10000)).ContinueWith(t => SetLabel());
     Task taskDelay2 = Task.Run(() => Task.Delay(15000)).ContinueWith(t => SetLabel()); 
     Task taskDelay3 = Task.Run(() => Task.Delay(20000)).ContinueWith(t => SetLabel()); 
     await Task.WhenAll(taskDelay1, taskDelay2, taskDelay3);
    

    【讨论】:

    • @PatrickHofman 我已更新答案以包含 ContinueWith 以设置标签
    • SetLabel 必须是async 方法二吗?
    • @Pedram:不。它将同步运行。
    【解决方案2】:

    并行并发之间有一个重要的区别。并行是指使用多个线程来做 CPU 密集型的工作。并行是并发的一种形式,但不是唯一的一种。另一种形式的并发是异步,它可以在不引入多个线程的情况下完成非 CPU 绑定的工作。

    在您的情况下,“下载”意味着 I/O 绑定操作,应该使用异步和并行性来完成。查看 HttpClient 类以进行异步下载。您可以使用Task.WhenAll 进行异步并发:

    private async Task RunDownload1Async()
    {
      label1.Text = "Pending";
      await Task.Delay(10000);
      label1.Text = "Finished";
    }
    
    private async Task RunDownload2Async()
    {
      label2.Text = "Pending";
      await Task.Delay(15000);
      label2.Text = "Finished";
    }
    
    private async Task RunDownload3Async()
    {
      label3.Text = "Pending";
      await Task.Delay(20000);
      label3.Text = "Finished";
    }
    
    private async Task RunDownloadsAsync()
    {
      await Task.WhenAll(RunDownload1Async(), RunDownload2Async(), RunDownload3Async());
    }
    

    这种方法可以避免创建不必要的线程,也不会使用过时的技术(ContinueWithDispatcher.Invoke)。也就是说,它并不完美,因为 GUI 逻辑与 RunDownloadNAsync 方法中的操作逻辑混合在一起。更好的方法是使用 IProgress<T>Progress<T> 来更新 UI。

    【讨论】:

      【解决方案3】:

      您可以在Task 完成后使用ContinueWith 执行操作。请注意,您可能需要在 ContinueWith 的表达式中调用 Dispatcher.Invoke 以防止跨线程调用。

      这是来自 WPF 应用程序的示例,使用 Dispatcher.Invoke

      private static async void Async()
      {
          Task taskDelay1 = Task.Run(() => Task.Delay(1000))
                            .ContinueWith(x => Dispatcher.Invoke(() => this.label1.Content = "One done"));
      
          Task taskDelay2 = Task.Run(() => Task.Delay(1500))
                            .ContinueWith(x => Dispatcher.Invoke(() => this.label2.Content = "Two done"));
      
          Task taskDelay3 = Task.Run(() => Task.Delay(2000))
                            .ContinueWith(x => Dispatcher.Invoke(() => this.label3.Content = "Three done"));
      
          await Task.WhenAll(taskDelay1, taskDelay2, taskDelay3);
      }
      

      正如Dirk 建议的那样,调用Dispatcher.Invoke 的替代方法是使用任务调度程序,例如TaskScheduler.FromCurrentSynchronizationContext() 如果你在 UI 线程上。

      【讨论】:

      • 调用Dispatcher.Invoke 的替代方法是使用任务调度程序,例如TaskScheduler.FromCurrentSynchronizationContext() 如果你在 UI 线程上。
      • @Dirk:如果你同意的话,添加它并注明出处。
      • @PatrickHofman 抱歉,您的回答似乎是正确的,但另一位先生早些时候发布了。
      • @Pedram:没问题。您的支持让一切顺利。谢谢。我需要更多的时间来准备一篇像样的帖子。
      猜你喜欢
      • 1970-01-01
      • 2019-10-07
      • 1970-01-01
      • 1970-01-01
      • 2015-10-06
      • 2020-03-02
      • 2016-06-04
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多