1

I have some working code. but I am not entirely sure what the best practice is, while working with Task.WhenAll. I found different blog post's using different approaches, but none of them stating why they went with their approach. The only "good" posts I found, was using Task.WhenAll, to fetch similar data and combining the result. But my use-case is I several data-sources with different data, that I have to do some work on.

The approach that I went with was:

var weatherTask = GetWeatherAsync();
var locationTask = GetLocationAsync();
var driversTask = GetDriversAsync();

await Task.WhenAll(weatherTask, locationTask, driversTask)
var weather = await weatherTask;
var location = await locaitonTask;
var drivers = await driversTask;

Mainly because I liked the way it looked / readability. But I also had thought about using the following other approach:

var weatherTask = GetWeatherAsync();
var locationTask = GetLocationAsync();
var driversTask = GetDriversAsync();

await Task.WhenAll(weatherTask, locationTask, driversTask)
var weather = weatherTask.Result;
var location = locaitonTask.Result;
var drivers = driversTask.Result;

The thought behind this approach was, that the tasks already was done and it would be save to use the .Result. But I stuck with the first approach. Because I was not entirely sure about the overhead of 'await' (if any).

So I came here in hope of some smart people knew the answer.

*** Edit I have looked at Awaiting multiple Tasks with different results

The answer to that post is:

You can also use Task.Result (since you know by this point they have all completed successfully). However, I recommend using await because it's clearly correct, while Result can cause problems in other scenarios.

But not if there is any performance, using one over the other.

******** Edit Taking the example to the extreme. Look at these 2 loops.

for(int i=0; i< 1.000.000.000; i++)
{
    var weatherTask = GetWeatherAsync();
    var locationTask = GetLocationAsync();
    var driversTask = GetDriversAsync();

    await Task.WhenAll(weatherTask, locationTask, driversTask)
    var weather = await weatherTask;
    var location = await locaitonTask;
    var drivers = await driversTask;
}

vs

for(int i=0; i< 1.000.000.000; i++)
{
    var weatherTask = GetWeatherAsync();
    var locationTask = GetLocationAsync();
    var driversTask = GetDriversAsync();

    await Task.WhenAll(weatherTask, locationTask, driversTask);
    var weather = weatherTask.Result;
    var location = locaitonTask.Result;
    var drivers = driversTask.Result;
}

Would they both be just as quick? Or would the Task.Result be much faster?

Kiksen
  • 1,559
  • 1
  • 18
  • 41
  • Does this answer your question? [Awaiting multiple Tasks with different results](https://stackoverflow.com/questions/17197699/awaiting-multiple-tasks-with-different-results) – picolino Dec 11 '19 at 10:06
  • Hi. It does not :) I had seen that question. But it doesn't go enough into detail why it is the right solution :) – Kiksen Dec 11 '19 at 11:13
  • why not? Using `await` is more appropriate because `.Result` can cause deadlocks in specific scenarios. For more info refer to [this link](https://stackoverflow.com/questions/27464287/what-is-the-difference-between-await-taskt-and-taskt-result). – picolino Dec 11 '19 at 11:19
  • 'can cause deadlocks in specific scenarios.' I know that. But not in this scenario. I am full aware I should not use .Result on a task that has not been awaited. My question is when I have awaited an entire list of Tasks. Would there be any performance using .Result, when there might be some overhead in await, an task that has already been completed. – Kiksen Dec 11 '19 at 11:22
  • If you interested for performance optimization, then I think using `.Result` will be more faster than using `await` clause. That's because `await` forces the compiler to use state-machine for forming correct call stacks while Exception handling. – picolino Dec 11 '19 at 11:26
  • @picolino -> I think that aswell. But I want to know for sure :) That is the entire reason behind the question. How big a overhead is there on await on an already ended task? I have found plenty of blog posts using both approaches. But no one has described it other than .Result might fail in other cases. I just want to get to know more about what happens under the hood. – Kiksen Dec 11 '19 at 11:31
  • @picolino 'because await forces the compiler to use state-machine for forming correct call stacks while Exception handling' However awaiting already finished tasks simply runs the code synchronously. So there is a chance the code will be optimized or the state-machine won't be used. I have no actual performance numbers but I guess there will be no or just slight performance difference between the two method. – János Pánczél Dec 11 '19 at 11:32
  • @JánosPánczél -> I had the same guess :) But it would be awesome if some Expert knew it :) Also part of the reason I stuck with Await. If some other developer saw .Result and start using it the wrong way. Rather have small overhead than having bad practices appearing. But still want to know if some time in the future I have to spin up 1.000.000.000 small tasks in a foreach in small batches. What would there be a good performance gain in using .Result after a WhenAll(). – Kiksen Dec 11 '19 at 11:38
  • I have added a new code example in the question. What if I had to run through this logic 1.000.000.000 times. What would the right approach be. – Kiksen Dec 11 '19 at 11:45
  • @Kiksen: at this point I'd suggest to use a Stopwatch and measure the time. :) – János Pánczél Dec 11 '19 at 11:51
  • @JánosPánczél -> Think I will spend some time later at home with BenchmarkDotnet to find out how big a deal it is. Then I will make answer to my own question then. – Kiksen Dec 11 '19 at 11:54
  • @Kiksen I interested in result. Waiting for you :) – picolino Dec 11 '19 at 11:57

2 Answers2

2

So I made a not entirely accurate test. But gave me a small indicator of what I expected.

The code:

enter image description here

The result after 10 runs in a for loop:

enter image description here

Await was always slightly slower. But not with much. For 1.000.000, it wasn't really too big a issue. But if your scale is higher than that, using .Result after Task.WhenAll might be a good idea :)

This test was run while without closing browsers, visual studio and other apps. So is by no means a clean test. Just a quick attempt without using BenchmarkDotnet

Kiksen
  • 1,559
  • 1
  • 18
  • 41
0

Probably more like worst than best practice, but it seems like a viable alternative....

        var weatherTask = Task.FromResult(new Weather());
        var locationTask = Task.FromResult(new Location());
        var driversTask = Task.FromResult(new Driver[0]);

        var results = await Task.WhenAll(
            Task.Run(async () => await weatherTask as object),
            Task.Run(async () => await locationTask as object),
            Task.Run(async () => await driversTask as object));

        var weather = (Weather)results[0];
        var location = (Location)results[1];
        var drivers = (Driver[])results[2];
Darragh
  • 2,526
  • 1
  • 23
  • 31