C# Async Await, Eventually: Thread Pools

glsolaria

G.L Solaria

Posted on July 3, 2021

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.

Hopefully I won't end up like this!

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>();
      }
  }
}
Enter fullscreen mode Exit fullscreen mode

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.

馃挅 馃挭 馃檯 馃毄
glsolaria
G.L Solaria

Posted on July 3, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related