Introduction
Task.Run
method is introduced in .NET Framework 4.5
, and it’s simple for us to use async
/await
keywords. Also, use this method can help us manage threads with ThreadPool
, so we can write asynchronous codes as simple as synchronous codes.
But, if Task.Run
method is abused, it will reduce the application’s performance drastically. In this article, we will introduce more details about the default thread pool task scheduler. And we will introduce the right way to use Task.Run
, to avoid reducing the application’s performance.
Detail
The original link is 了解 .NET 的默认 TaskScheduler 和线程池(ThreadPool)设置,避免让 Task.Run 的性能急剧降低. But this blog was written in Chinese, so I translate its content to English. Waterlv is an MVP(Microsoft Most Valuable Professional), and he is good at .NET Core\WPF\.NET. Here is his Blog.
How to use Task.Run
method
- For the
IO
operation, developers should try their best to useAsync
method provided inIO
class instead of usingTask.Run
to execute anIO
operation. (Task.Run
will take up thread pool resources.) - If there are not any
Async
methods to finish a time-consuming operation ofIO
, we should set the value ofCreateOptions
toLongRunning
. - We recommend using
Task.Run
method to execute short-running tasks.
In the subsequent paragraphs, we will analyze the reasons for these usages posted above.
Demo
Before analyzing, we wrote a test application. In this application, it runs 10 asynchronous tasks with Task.Run
method, and in each task, there is a Thread.Sleep(5000)
method to suspend thread for 5 seconds.
It is easy to find that the starting time of these 10 tasks is different, even we run these tasks at the same time. The first 4 tasks started immediately, then it started a new asynchronous task every second.
Here is the code of this demo.
1 | class Program |
TaskScheduler
The reason for these tasks above couldn’t start right away is that the Task
class use TaskScheduler
to schedule threads. If developers don’t set the value of TaskScheduler
, it will use ThreadPoolTaskScheduler
as its default value. And, the Task.Run
method uses the default scheduler of .NET
, and you can get it by TaskCheduler.Default
.
For more information, visit source code of .NET Core
– ThreadPoolTaskScheduler.QueueTask.
So, the setting in the thread pool will determine when to start a new thread to execute the task.
ThreadPool
Developers can get the minimum number of worker threads and the asynchronous ‘IO’ threads by ThreadPool.GetMinThreads
method. Also, we can get their maximum number by Thread.GetMaxThreads
method. And we can call the Set...
methods to set the value of the minimum or the maximum number of threads.
For more details ,visit ThreadPool.GetMinThreads(Int32, Int32) Method (System.Threading) - Microsoft Docs
So, here are some items of how the ThreadPool
works.
- The thread pool creates new worker thread or IO thread on demand until it reaches the minimum number of each category.
- By default, the minimum number of threads is the same as the number of processors on the computer.
- After reaching the minimum value, the thread pool can create other threads or just wait until some tasks finished.
- When the demand is low, the number of threads in the thread pool can be less than the minimum value.
That is why the tasks of the demo posted above can’t start at the same time. In my computer (4-processors), the minimum value of threads is 4, so the first 4 tasks can be executed immediately. When the number of thread reaches 4 and there are still no threads finished, the thread pool will try to wait for other tasks to complete. But, if there are still no threads finished after 1 second, the thread pool will create a new thread to execute the new task. But when there are some threads finished, it will use these threads to execute the new tasks instead of creating new threads.
But, it is important to notice that the number of threads should be less than the maximum value.
The recommended usages
After learning about the default action of ThreadPoolTaskScheduler
, we can take advantage of the thread pool by doing these things:
- For the
IO
operation, try to use the methods withAsync
prefix provided byIO
class to occupyIO
threads in the thread pool instead of normal threads( don’t useTask.Run
to consume thread pool resources) - If there are not any
Async
methods to finish a time-consuming operation ofIO
, we should set the value ofCreateOptions
toLongRunning
.(After setting this value, it will create a new thread instead of using the thread in the thread pool) - We recommend using
Task.Run
method to execute short-running tasks.
References
- TaskScheduler Class (System.Threading.Tasks) - Microsoft Docs
- TaskCreationOptions Enum (System.Threading.Tasks) - Microsoft Docs
- Parallel Tasks - Microsoft Docs?wt.mc_id=MVP)
- Attached and Detached Child Tasks - Microsoft Docs
- ThreadPool.GetMinThreads(Int32, Int32) Method (System.Threading) - Microsoft Docs
- Managed Threading Best Practices - Microsoft Docs