CLR Threads Via C# Part 2 – The Thread Pool

In this post I’ll be covering the Thread Pool as part 2 in my CLR threading series.

Advertisements

In the last post I talked about the basics of CLR threads by taking a look at the Thread class. We discussed background and foreground threads as well, and that dovetails nicely into the thread pool, so I wanted to go ahead and cover the thread pool in this next part.

What’s a thread pool?

In the CLR, there is support for using threads as a pooled resource, also known as, a thread pool. That answers the question of what the thread pool is, but a better question is why? The main reason (as with anything in software development) is a different set of pros and cons. The thread pool has three distinct traits, two of which I consider to be advantageous, one which is a situational pro or con.

  • The thread pool allows you to “borrow” threads from the thread pool, this is especially useful for quick, concurrent operations.
  • The CLR itself manages the lifetime of the thread, from creation to destruction of the thread, and it adds or removes threads from the thread pool based on demand, meaning you in theory will have an optimal number of threads in the thread pool.
  • Threads in the thread pool are background threads by default.

Using thread pool threads

Of course you could know everything in the world about the thread pool, and if you can’t use it then your knowledge is pretty useless, so let’s cover how to use them next. There are three main ways of using thread pool threads:

  • ThreadPool.QueueUserWorkItem
  • Delegate.BeginInvoke
  • Async I/O

ThreadPool.QueueUserWorkItem

If we drill down into the ThreadPool class, we can see two implementations of QueueUserWorkItem, one that takes a WaitCallback, and another that takes a WaitCallback and an object “state”. As we covered in the previous post, “object state” in this context just refers to a single piece of data or a single object you can pass to the method you wish for the thread to execute. So basically, we can either simply pass a method to execute, or a method with a parameter. I’ll be reusing the RandomMethodToRun method from the previous post to demonstrate this:

        public static void RandomMethodToRun(object threadNumber)
        {
            try
            {
                Thread.Sleep(1000);
                Console.WriteLine("Thread {0} executed to completion", threadNumber);
            }
            catch(ThreadInterruptedException e)
            {
                Console.WriteLine(e.Message);
            }
        }

Now let’s queue up a thread from the thread pool with QueueUserWorkObject:

            ThreadPool.QueueUserWorkItem(RandomMethodToRun);

For good measure, let’s also queue up a thread that takes a state object and see the differences in the two threads output with our method:

            ThreadPool.QueueUserWorkItem(RandomMethodToRun, 2);

When I run this program, I get the following output:

Thread  executed to completion
Thread 2 executed to completion

Note that since no state object was passed for the callback method for the first thread, that portion of the output is empty. Let’s add 1 as the second argument for the first thread so that we can see how the order of execution works:

Thread 2 executed to completion
Thread 1 executed to completion

After a few runs, this is the output I got, so we can concretely say that the threadpool, just like creating and starting an instance of the thread class, does not guarantee an order of execution. This is especially contrary to what you may assume would happen, because as the name implies, QueueUserWorkItem does infact use a FIFO queue, so even though your threads are being run in order, execution is not guaranteed to happen in any particular order, for instance a thread may take too long and the CLR may decide it’s best to go ahead and run the next thread before the previous thread gets a chance to finish up.

Another thing I mentioned previously is that thread pool threads are background threads by default, I’m going to set a sleep on the RandomMethodToRun method to 5000 so we pause for 5 seconds, this will give us plenty of time to close the application while the threads are sleeping. If they truly are background threads, they won’t block application shut down, and sure enough when I run the program, this is the case.

Delegate.BeginInvoke

Delegates are classes that represent method calls which have a signature and a target instance. Whenever you create a delegate method, the compiler generates a BeginInvoke method that matches the specified signature of the delegate, and it goes ahead and trusts that the actual implementation of that method is provided by the CLR at runtime. If that explanation was confusing and you know C or C++, let me explain delegates this way. A delegate is a function declaration that you can assign any function that matches the signature of said declaration as the definition, and then you can use the delegate as a type. The delegate will be provided a BeginInvoke method by default, and when you assign a method to the delegate, that method will be run with the thread pool. Why is this beneficial? Because it encapsulates the entirety of thread management for you, The threadpool uses threads for you so you don’t have to manage threads, and delegates use the threadpool so you don’t have to worry about the thread pool.

If delegates still seem hazy to you, maybe some code will help make it easier for you. Let’s begin by creating a delegate (can’t use what you don’t have), I placed this code right outside the program class generated by Visual Studio:

delegate void AddTwoNumbersDelegate(int num1, int num2);

If you remember from the previous post, and even from earlier when we discussed QueueUserWorkItem, you’ll remember that we could only pass a single piece of data, and the method that the thread used had to take an object as it’s argument. In this case, we can use typed arguments, and as many as we please, this is already an obvious benefit of using delegates. Let’s make a method to output the sum of two numbers:

        public static void AddTwoNumbers(int num1, int num2)
        {
            Console.WriteLine("{0} + {1} = {2} - brought to you by, not the main thread", 
                              num1, num2, num1 + num2);
        }

Simple enough, now let’s actually create an instance of our delegate and assign AddTwoNumbers to it so we can have it be run when BeginInvoke is called:

            AddTwoNumbersDelegate addFromThread = AddTwoNumbers;
            addFromThread.BeginInvoke(5, 5, null, null);

Here, we create an instance of the AddTwoNumbersDelegate, and we assign it the AddTwoNumbers method. Note that the signatures of both the delegate and the delegate method are void return types, and take two ints, because of this the delegate can be assigned this method, and whenever we call BeginInvoke, the body of AddTwoNumbers is what will run, and this is implemented at runtime. The two final arguments in begin invoke I passed null to, and that’s because for now we don’t need them and won’t be covering them. As you would expect, this is the output:

5 + 5 = 10 - brought to you by, not the main thread

Note that the big disadvantage of using a delegate is it will run it’s method on a background thread, so it’s not safe to trust delegates for doing things that are mission critical.

Async I/O

This is a different post in my opinion, so I’ll be glazing over it. Async IO offers scalable interfacing with the thread pool by doing non-blocking, IO centric operations, examples of this would be things like file IO with BeginRead, BeginWrite, etc. That’s really all I’m going to say on this topic as again, I feel that async IO is a topic of it’s own.

Wrap Up

That’s all I can think to cover regarding the thread pool in the CLR, if you have recommendations of things I could cover, let me know and I’ll either cover it in another post or amend this one. To sum this all up, the thread pool manages thread lifetime for you, and allows you to use threads in the thread pool to execute code in a few different ways without having to use threads directly. Thanks again for taking the time to read, and if you have any positive or negative criticisms, feel free to reach out. Thanks, and until next time.

  1. […] on April 10, 2018by admin submitted by /u/Trevor266 [link] [comments] No comments […]

    Like

    Reply

  2. […] the last post, we discussed the thread pool, and in the post prior to that, we discussed the […]

    Like

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Advertisements
Advertisements
%d bloggers like this: