Wednesday, May 7, 2014

How To Limit The Thread Count Use By The Task Library?

Today I want to cover the topic about how to limit the thread use by the Task library which is available in .NET 4.5. This post is related to my previous async and await syntax post.

First of all, do you know that when one Task start to run, a new thread will be spawn for that task? Task is supposed to be used for asynchronous operation, however, with its thread spawning behavior, it also can be used for multi-threading operation. More detail info, check out this Task Parallelism article.

There are 3 ways to create a Task:

Option 1:

Using Task.Factory.

Task t1 = Task.Factory.StartNew(() =>
{
    Console.WriteLine(DateTime.Now.ToString() + " - Task fired.");
    Thread.Sleep(5000);

});


Option 2:

Using Task.Start() method.

Task t2 = new Task(() =>
{
    Console.WriteLine(DateTime.Now.ToString() + " - Task fired.");
    Thread.Sleep(5000);
});


t2.Start();


Option 3:

Using Task.Run() method.

Task t3 = Task.Run(() =>
{
    Console.WriteLine(DateTime.Now.ToString() + " - Task fired.");
    Thread.Sleep(5000);

});


To me, the option 1 and 2 are the same, though the MSDN article suggested to use Factory when you do not want to separate the task creation and scheduling, but using Task explicitly also can do the job. May be Factory pattern look elegant? :P

Option 3 is different. The Task.Run() method only uses the available thread from the thread pool. Unlike option 1 and 2, both method spawn a new thread whenever a Task is started. Sometime, we need to limit the thread spawning because thread is resource expensive, it will consume all the memory and processing power that you have. The reason is the processor can only process one thing at a time, and due to the speed of the processor nowadays is extremely fast, it looks like all the tasks are processing concurrently. In fact, the processor is actually keep switching between threads to process bit by bit until the end and that created the illusion to many people think that their tasks are running concurrently. Even with the Hyper-Threading processor technology today, my Intel i7 4 cores 8 threads processor can only process 8 threads concurrently.



Therefore, we cannot set the maximum thread limit less than the logical processor count. For my processor, it has 8 logical processor, I am not allowed to set the maximum thread value less than 8. Also, the default minimum thread count is following logical processor count which is 8.

You can use the following method to check your current thread pool thread limit:

ThreadPool.GetMinThreads(out workerThreadCount, out ioThreadCount);
ThreadPool.GetMaxThreads(out workerThreadCount, out ioThreadCount);

Then, change the thread pool limit with the following code:

//First parameter is the number of thread in the pool
//Second parameter is the number of async I/O thread in the pool
//Both value cannot be less than your logical processor count
ThreadPool.SetMaxThreads(10, 10);

Then, combine with using Task.Run() method, you will notice only 10 threads will be spawn and working concurrently.



See the highlighted red box, there are 8 threads are actually running concurrently even I have set the max worker thread limit as 10. The other 2 thread spawn slightly later.

The following is the final piece of code:

class Program
{
    static void Main(string[] args)
    {
        int workerThreadCount;
        int ioThreadCount;

        ThreadPool.GetMinThreads(out workerThreadCount, out ioThreadCount);

        Console.WriteLine("Default min worker thread: " + workerThreadCount.ToString());
        Console.WriteLine("Default min I/O thread: " + ioThreadCount.ToString());

        ThreadPool.GetMaxThreads(out workerThreadCount, out ioThreadCount);

        Console.WriteLine("Default max worker thread: " + workerThreadCount.ToString());
        Console.WriteLine("Default max I/O thread: " + ioThreadCount.ToString());

        Console.ReadKey();

        //First parameter is the number of thread in the pool
        //Second paramter is the number of async I/O thread in the pool
        //Both value cannot be less than your logical processor count
        ThreadPool.SetMaxThreads(10, 10);

        ThreadPool.GetMinThreads(out workerThreadCount, out ioThreadCount);

        Console.WriteLine("Current min worker thread: " + workerThreadCount.ToString());
        Console.WriteLine("Current min I/O thread: " + ioThreadCount.ToString());

        ThreadPool.GetMaxThreads(out workerThreadCount, out ioThreadCount);

        Console.WriteLine("Current max worker thread: " + workerThreadCount.ToString());
        Console.WriteLine("Current max I/O thread: " + ioThreadCount.ToString());

        for (int i = 1; i <= 100; i++)
        {
            //Task Factory
            //Task t1 = Task.Factory.StartNew(() =>
            //{
            //    Console.WriteLine(DateTime.Now.ToString() + " - Task fired.");
            //    Thread.Sleep(10000);
            //});

            //Normal Task Start
            //Task t2 = new Task(() =>
            //{
            //    Console.WriteLine(DateTime.Now.ToString() + " - Task fired.");
            //    Thread.Sleep(10000);
            //});

            //t2.Start();

            //Thread Pool Task Run
            Task t3 = Task.Run(() =>
            {
                Console.WriteLine(DateTime.Now.ToString() + " - Task fired.");
                Thread.Sleep(10000);
            });
        }

        Console.ReadKey();
    }
}

Running the program long enough, you will see the 10 threads limit behavior since I purposely make every thread to sleep for 10 seconds. You can uncomment the code that use Factory and Task.Start(), you will notice they spawn threads uncontrollably.








9 comments:

Send Transactional SMS with API

This post cover how to send transactional SMS using the Alibaba Cloud Short Message Service API. Transactional SMS usually come with One Tim...