Still going through a learning phase with C# and ran into a question I needed help with. Considering the following code:
private async Task<String> PrintTask()
{
await Task.Delay(3000);
return "Hello";
}
private async void SayHelloTwice()
{
string firstHello = await PrintTask();
string secondHello = await PrintTask();
Console.WriteLine(firstHello);
Console.WriteLine(secondHello);
}
Right now SayHelloTwice() will take 6 seconds to complete. However, I want the retrieval tasks to be ran in parallel so that it only takes 3 seconds to complete. How would I refactor my code to achieve that? Thanks!
The correct way to do this (without risk of deadlocks) is to use Task.WhenAll
private async void SayHelloTwice()
{
string[] results = await Task.WhenAll(
PrintTask(),
PrintTask());
Console.WriteLine(results[0]);
Console.WriteLine(results[1]);
}
Quite often library writers will endeavor to make the "callback" code in a Task run on the original thread that called it. This is often because objects can only be accessed from a single thread.
When using the blocking calls such as Task.Result
and Task.WaitAll
the thread is suspended (cannot do additional work) until the Task
completes.
However as previously mentioned, often times the Task
will be waiting for the calling thread to free up, so it can be used to complete the task.
So, in the first case, the outter "Task" holds the Thread, and is waiting for completion.
The second case, the inner "Task" is waiting for the Thread to complete.
Ergo neither will ever run to completion.
In general what you want is to start both tasks and wait afterwards. A straightforward way is:
private async void SayHelloTwice()
{
// Start the tasks, don't wait
Task<string> firstHello = PrintTask();
Task<string> secondHello = PrintTask();
// Wait for results
string firstResult = await firstHello;
string secondResult = await secondHello;
// Print results
Console.WriteLine(firstResult);
Console.WriteLine(secondResult);
}
What happens here is the call to PrintTask()
will start execution of the method and once the execution reaches the first await
that does an actual async operation, the running task will be returned and assigned to firstHello.
The same goes for secondHello.
Then you wait for both to complete and print the result.
This implementation is just an example to simplify how things work. In real-world code you should probably use the Task.WhenAll
to wait for all running tasks
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With