I am reviewing some code and trying to come up with a technical reason why you should or should not use Task.WhenAll(Tasks[])
for essentially making Http calls in parallel. The Http calls call a different microservice and I guess one of the calls may or may not take some time to execute... (I guess I am not really interested in that). I'm using BenchmarkDotNet to give me an idea of there is any more memory consumed, or if execution time is wildly different. Here is an over-simplified example of the Benchmarks:
[Benchmark]
public async Task<string> Task_WhenAll_Benchmark()
{
var t1 = Task1();
var t2 = Task2();
await Task.WhenAll(t1, t2);
return $"{t1.Result}===={t2.Result}";
}
[Benchmark]
public async Task<string> Task_KeepItSimple_Benchmark()
{
return $"{await Task1()}===={await Task2()}";
}
Task1
and Task2
methods are really simple (I have a static HttpClient
in the class)
public async Task<string> Task1()
{
using (var request = await httpClient.GetAsync("http://localhost:8000/1.txt"))
{
return $"task{await request.Content.ReadAsStringAsync()}";
}
}
public async Task<string> Task2()
{
using (var request = await httpClient.GetAsync("http://localhost:8000/2.txt"))
{
return $"task{await request.Content.ReadAsStringAsync()}";
}
}
And my results
Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|
Task_WhenAll_Benchmark | 1.138 ms | 0.0561 ms | 0.1601 ms | - | - | - | 64 KB |
Task_KeepItSimple_Benchmark | 1.461 ms | 0.0822 ms | 0.2331 ms | - | - | - | 64 KB |
As you can see, memory not an issue and there isn't a great deal of time in the execution either.
My question really is, is there a technical reason why you should or not use Task.WhenAll()? Is it just a preference?
I came across Async Guidance from a guy on the .net core team but it hasn't really covered this scenario.
Edit: this is .net framework (4.6.1) rather than core!
Edit 2: Update one of the benchmarks as suggested in a comment below.
I updated the benchmark for the KeepItSimple approach...
[Benchmark]
public async Task<string> Task_KeepItSimple_Benchmark()
{
var t1 = Task1();
var t2 = Task2();
return $"{await t1}===={await t2}";
}
and have the following results:
Method | Mean | Error | StdDev | Gen 0 | Gen 1 | Gen 2 | Allocated |
---|---|---|---|---|---|---|---|
Task_WhenAll_Benchmark | 1.134 ms | 0.0566 ms | 0.1613 ms | - | - | - | 64 KB |
Task_KeepItSimple_Benchmark | 1.081 ms | 0.0377 ms | 0.1070 ms | - | - | - | 64 KB |
And now I am even more confused - how is the execution faster (albeit a tiny amount!)? I thought execution of the code started when you await
the result...