Reputation: 7278
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
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
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
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