disasterkid
disasterkid

Reputation: 7278

3 parallel tasks each awaiting their own result

I would like to try using background workers. I am interesting to use async/await. I have 3 parallel tasks.

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 ???
}

Let's assume each of these Tasks changes the value of 3 Labels (label1, label2, label3) on a form when they are done. i.e. all labels are set to "Pending" and when each task is done, their corresponding label value will change to "Finished".

I would like to await each task accordingly so that they each do their own corresponding task. That means if taskDelay1's job is finished set label1's text to "Finished" while taskDelay2 and taskDelay3 are still pending. But if I put 3 awaits there, the program will wait for all of them to finish their task and then continue the rest of the code.

How can I continue the rest of the code in which each label only waits for its own task to finish and then decide what to do?

Upvotes: 1

Views: 96

Answers (3)

Stephen Cleary
Stephen Cleary

Reputation: 456527

There's an important distinction between parallel and concurrent. Parallel refers to using multiple threads to do CPU-bound work. Parallelism is one form of concurrency, but not the only one. Another form of concurrency is asynchrony, which can do non-CPU-bound work without introducing multiple threads.

In your case, "downloads" imply an I/O-bound operation, which should be done with asynchrony and not parallelism. Check out the HttpClient class for asynchronous downloads. You can use Task.WhenAll for asynchronous concurrency:

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

This approach avoids creating unnecessary threads and also does not use outdated techniques (ContinueWith, Dispatcher.Invoke). That said, it's not perfect, since the GUI logic is mixed with the operational logic in the RunDownloadNAsync methods. A better approach would be to use IProgress<T> and Progress<T> for updating the UI.

Upvotes: 4

Patrick Hofman
Patrick Hofman

Reputation: 156978

You can use ContinueWith to execute an action after the Task is completed. Note that you might need to call Dispatcher.Invoke in the expression in the ContinueWith to prevent cross-thread calls.

Here is a sample from a WPF application, using 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);
}

As Dirk suggested, an alternative to calling Dispatcher.Invoke would be to use a task scheduler, e.g. TaskScheduler.FromCurrentSynchronizationContext() if you're on the UI thread.

Upvotes: 1

Vsevolod Goloviznin
Vsevolod Goloviznin

Reputation: 12324

You should use Task.WhenAll method to wait for all of them and ContinueWith to execute operation when the task is completed.

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

Upvotes: 5

Related Questions