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.
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 ...
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.
Top comments (0)