In my last article, 100 simulated one-second waits finished in about one second. That worked because each wait was Task.Delay, which doesn't hold a thread while it waits. So I ran the obvious follow-up: what if each task waits by blocking a thread instead?
The answer was a result I could predict only in direction, not in size: about five seconds instead of one. This article shows the numbers, explains why the figure came out near five, and covers the places where this mistake hides in real code.
The Experiment
csharp
using System.Diagnostics;
Console.WriteLine($"Logical processors: {Environment.ProcessorCount}");
Console.WriteLine($"Pool threads at start: {ThreadPool.ThreadCount}");
void Report(string label, long ms) =>
Console.WriteLine($"{label,-30} {ms,7} ms pool threads: {ThreadPool.ThreadCount}");
var sw = Stopwatch.StartNew();
await Task.WhenAll(Enumerable.Range(1, 100).Select(_ => Task.Delay(1000)));
Report("100 x Task.Delay(1000)", sw.ElapsedMilliseconds);
sw.Restart();
await…