C# Async Await, Eventually: Thread Pools

In my previous post in the series I covered what Asynchronous Programming is and introduced threads (https://dev.to/glsolaria/c-async-await-eventually-asynchronous-programming-5h46). Now I am going to eventually get to an explanation of what the Synchronization Context (now don't be scared) is but to do that I need to answer a different question.

Trust me I will bring this together and get to Async/Await eventually and hopefully you will be in a better position to understand what is happening under the hood.

What is the Thread Pool?

Simply: What is the Thread Pool?

Threads are resource intensive critters. It takes a relatively long time to create them. By creating a pool of them, the Thread Pool has a ready set of threads to use to execute your code.

Humour me with a Thread Pool analogy ...

You asked for it so here I go! Imagine you have pool of secretaries that can do work for you: photocopy, file, answer phones etc. Getting a new secretary takes time to advertise, interview, then hire so you decide to have a set already ready to go. When the work comes in, you allocate a secretary to do the work and then the secretary returns to the pool waiting for the next job. (The pool could be a swimming pool or a pool table but basically it's where the secretaries hang out waiting for new work). In this analogy you are running a Secretary Pool. Just replace secretary with Thread and hopefully you can see where I am going with this.
Again I hope I am not missing the mark

More complicated: What is the Thread Pool?

Threads are resource intensive critters. It takes a while to start them because it involves allocating resources and may involve communication between the kernel space and the runtime. I have included the following code for your reference but I really want you to focus on the bar plot this code produces ...

[RPlotExporter]
[SimpleJob(RunStrategy.ColdStart, RuntimeMoniker.Net50, baseline: true)]
public class ThreadPoolBaseliner
{
  private Thread[] simpleThreadPool; 

  [Params(1, 2, 4, 8, 16, 32, 64, 128)] public int N;

  [IterationSetup]
  public void Setup() => simpleThreadPool = new Thread[N];

  [Benchmark]
  public void ThreadCreation() => CreateThreads(N);

  [Benchmark]
  public void ForLoopAddition() => LoopAdditionFor(N);

  public void CreateThreads(int numberOfThreads)
  {
      for (int ii = 0; ii < numberOfThreads; ++ii)
      {
        simpleThreadPool[ii] = new Thread(() => 
          { Thread.Sleep(0); });
      }
  }

  public void LoopAdditionFor(int numberOfIterations)
  {
      int total = 0;
      for (int ii = 0; ii < numberOfIterations; ++ii) total += 42;
  }

  private class Program
  {
      public static void Main(string[] args)
      {
        var summary = BenchmarkRunner.Run<ThreadPoolBaseliner>();
      }
  }
}

I used https://github.com/dotnet/BenchmarkDotNet to produce the following bar plot ...
Starting threads can take some time
The time is shown in microseconds and the plot compares the benchmark results between a simple for loop and thread creation where the number of iterations starts at 1 then doubles until we get to 128. Hopefully you can see that starting threads can be a costly exercise.

So in conclusion, a Thread Pool manages thread creation, execution, and destruction in such a way to balance the resource costs against the benefits of asynchronous execution.

33