How many threads should I use?Number of Relational Threads = Number of CPU Cores* (Local Compute Time + Wait Time) / Local Compute Time
Below is a comparison of Task.Factory.StartNew and TaskHelper.LargeTask.Run
1. Task.Factory.StartNew uses the TaskCreationOptions.LongRunning parameter
Code:
private int n = 50000; //Issue Size private int t = 25; //waiting time private int pageSize = 1000; //Print Page Breaks private void TestTaskStartNew() { Task.Factory.StartNew(() => { Stopwatch stopwatch = Stopwatch.StartNew(); List<Task> taskList = new List<Task>(); for (int i = 0; i <= n; i++) { Task task = Task.Factory.StartNew((obj) => { Thread.Sleep(t); //waiting time int index = (int)obj; if (index % pageSize == 0) { this.TryInvoke2(() => { textBox1.AppendText(index.ToString() + " "); }); } }, i, TaskCreationOptions.LongRunning); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); this.TryInvoke2(() => { textBox1.AppendText(string.Format("\r\n[Task.Factory.StartNew Issue Size:{0} waiting time:{1} Time consuming:{2}Seconds)\r\n", n, t, stopwatch.Elapsed.TotalSeconds)); }); }); } private void TestTaskHelper() { Task.Factory.StartNew(() => { Stopwatch stopwatch = Stopwatch.StartNew(); List<Task> taskList = new List<Task>(); for (int i = 0; i <= n; i++) { Task task = TaskHelper.LargeTask.Run((obj) => { Thread.Sleep(t); //waiting time int index = (int)obj; if (index % pageSize == 0) { this.TryInvoke2(() => { textBox1.AppendText(index.ToString() + " "); }); } }, i); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); this.TryInvoke2(() => { textBox1.AppendText(string.Format("\r\n[TaskHelper.LargeTask.Run {3}Thread Problem Size:{0} waiting time:{1} Time consuming:{2}Seconds)\r\n", n, t, stopwatch.Elapsed.TotalSeconds, TaskHelper.LargeTask.ThreadCount)); }); }); }
Test results:
0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 11000 12000 13000 14000 15000 16000 17000 18000 19000 20000 21000 22000 23000 24000 25000 26000 27000 28000 29000 30000 31000 32000 33000 34000 35000 36000 37000 38000 39000 40000 41000 42000 43000 44000 45000 46000 47000 48000 49000 50000
[TaskHelper.LargeTask.Run 128 Thread Problem Size: 50000 Wait Time: 25 Time: 10.5975181 Seconds]
0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 11000 12000 13000 14000 15000 16000 17000 18000 19000 20000 21000 22000 23000 24000 25000 26000 27000 28000 29000 30000 31000 32000 33000 34000 35000 36000 37000 38000 39000 40000 41000 42000 43000 44000 45000 46000 47000 48000 49000 50000
[Task.Factory.StartNew Problem Size: 50000 Wait Time: 25 Time: 8.2380754 Seconds]
0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 11000 12000 13000 14000 15000 16000 17000 18000 19000 20000 21000 22000 23000 24000 25000 26000 27000 28000 29000 30000 31000 32000 33000 34000 35000 36000 37000 38000 39000 40000 41000 42000 43000 44000 45000 46000 47000 48000 49000 50000
[TaskHelper.LargeTask.Run 128 Thread Problem Size: 50000 Wait Time: 25 Time: 10.4376939 Seconds]
0 1000 2000 3000 4000 5000 6000 7000 8000 9000 10000 11000 12000 13000 14000 15000 16000 17000 18000 19000 20000 21000 22000 23000 24000 25000 26000 27000 28000 29000 30000 31000 32000 33000 34000 35000 36000 37000 38000 39000 40000 41000 42000 43000 44000 45000 46000 47000 48000 49000 50000
[Task.Factory.StartNew Problem Size: 50000 Wait Time: 25 Time: 9.2322552 Seconds]
Test result description:
My computer's CPU is i5-8265U, 4 cores, 8 threads
Setting the appropriate number of threads based on wait time is beneficial to TaskHelper.LargeTask.Run
CPU s using TaskHelper.LargeTask.Run run-time are under 5%, 30% for the instantaneous creation of 128 threads, and nearly 100% for the Task.Factory.StartNew run-time.
Resource Release: Task.Factory.StartNew releases the number of threads immediately after running with the TaskCreationOptions.LongRunning parameter and the number of handles is not immediately released. TaskHelper.LargeTask.Run provides a manual release method to release the number of threads and handles immediately, but manual calls are required to release them
2. Task.Factory.StartNew does not use the TaskCreationOptions.LongRunning parameter
Code:
private int n = 2000; //Issue Size private int t = 100; //waiting time private int pageSize = 100; //Print Page Breaks private void TestTaskStartNew() { Task.Factory.StartNew(() => { Stopwatch stopwatch = Stopwatch.StartNew(); List<Task> taskList = new List<Task>(); for (int i = 0; i <= n; i++) { Task task = Task.Factory.StartNew((obj) => { Thread.Sleep(t); //waiting time int index = (int)obj; if (index % pageSize == 0) { this.TryInvoke2(() => { textBox1.AppendText(index.ToString() + " "); }); } }, i); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); this.TryInvoke2(() => { textBox1.AppendText(string.Format("\r\n[Task.Factory.StartNew Issue Size:{0} waiting time:{1} Time consuming:{2}Seconds)\r\n", n, t, stopwatch.Elapsed.TotalSeconds)); }); }); } private void TestTaskHelper() { Task.Factory.StartNew(() => { Stopwatch stopwatch = Stopwatch.StartNew(); List<Task> taskList = new List<Task>(); for (int i = 0; i <= n; i++) { Task task = TaskHelper.LargeTask.Run((obj) => { Thread.Sleep(t); //waiting time int index = (int)obj; if (index % pageSize == 0) { this.TryInvoke2(() => { textBox1.AppendText(index.ToString() + " "); }); } }, i); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); this.TryInvoke2(() => { textBox1.AppendText(string.Format("\r\n[TaskHelper.LargeTask.Run {3}Thread Problem Size:{0} waiting time:{1} Time consuming:{2}Seconds)\r\n", n, t, stopwatch.Elapsed.TotalSeconds, TaskHelper.LargeTask.ThreadCount)); }); }); }
Test results:
0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000
[TaskHelper.LargeTask.Run 96 Thread Problem Size: 2000 Wait Time: 100 Time: 2.1529565 Seconds]
0 2000 100 200 300 400 500 600 700 800 900 1900 1000 1100 1200 1300 1400 1500 1600 1700 1800
[Task.Factory.StartNew Problem Size: 2000 Wait Time: 100 Time: 17.309869 Seconds]
0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900 2000
[TaskHelper.LargeTask.Run 96 Thread Problem Size: 2000 Wait Time: 100 Time: 2.143763 Seconds]
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
[Task.Factory.StartNew Problem Size: 2000 Wait Time: 100 Time: 8.8674353 Seconds]
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
[Task.Factory.StartNew Problem Size: 2000 Wait Time: 100 Time: 6.5490833 Seconds]
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
[Task.Factory.StartNew Problem Size: 2000 Wait Time: 100 Time: 5.1381533 Seconds]
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
[Task.Factory.StartNew Problem Size: 2000 Wait Time: 100 Time: 4.434294 Seconds]
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
[Task.Factory.StartNew Problem Size: 2000 Wait Time: 100 Time: 4.329009 Seconds]
2000 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
[Task.Factory.StartNew Problem Size: 2000 Wait Time: 100 Time: 3.6231239 Seconds]
2000 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
[Task.Factory.StartNew Problem Size: 2000 Wait Time: 100 Time: 3.6303149 Seconds]
Test conclusion:
Task.Factory.StartNew runs a large number of time-consuming tasks without the TaskCreationOptions.LongRunning parameter, and the number of threads increases slowly, which can take a long time. If the thread pool is exhausted, or if the thread pool is not exhausted but has a large number of time-consuming tasks, other tasks will delay calling Task.Factory.StartNew
I thought for a day that it would be better not to share a thread pool with multiple tasks. One task, one thread pool, does not interfere with each other. TaskHelper.LargeTask.Run was written in this way, I don't know if there is a problem
Attachment:
LimitedTaskScheduler code:
using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; namespace Utils { public class LimitedTaskScheduler : TaskScheduler, IDisposable { #region External methods [DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")] public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize); #endregion #region Variable Attribute Events private BlockingCollection<Task> _tasks = new BlockingCollection<Task>(); List<Thread> _threadList = new List<Thread>(); private int _threadCount = 0; private int _timeOut = Timeout.Infinite; private Task _tempTask; public int ThreadCount { get { return _threadCount; } } #endregion #region Constructor public LimitedTaskScheduler(int threadCount = 10) { CreateThreads(threadCount); } #endregion #region override GetScheduledTasks protected override IEnumerable<Task> GetScheduledTasks() { return _tasks; } #endregion #region override TryExecuteTaskInline protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) { return false; } #endregion #region override QueueTask protected override void QueueTask(Task task) { _tasks.Add(task); } #endregion #region Resource Release /// <summary> /// Resource Release /// If a task is still executing, it will be raised on the thread calling this method System.Threading.ThreadAbortException,Please use Task.WaitAll Wait until the task finishes executing before calling the method /// </summary> public void Dispose() { _timeOut = 100; foreach (Thread item in _threadList) { item.Abort(); } _threadList.Clear(); GC.Collect(); GC.WaitForPendingFinalizers(); if (Environment.OSVersion.Platform == PlatformID.Win32NT) { SetProcessWorkingSetSize(System.Diagnostics.Process.GetCurrentProcess().Handle, -1, -1); } } #endregion #region Create Thread Pool /// <summary> /// Create Thread Pool /// </summary> private void CreateThreads(int? threadCount = null) { if (threadCount != null) _threadCount = threadCount.Value; _timeOut = Timeout.Infinite; for (int i = 0; i < _threadCount; i++) { Thread thread = new Thread(new ThreadStart(() => { Task task; while (_tasks.TryTake(out task, _timeOut)) { TryExecuteTask(task); } })); thread.IsBackground = true; thread.Start(); _threadList.Add(thread); } } #endregion #region cancel all /// <summary> /// cancel all /// </summary> public void CancelAll() { while (_tasks.TryTake(out _tempTask)) { } } #endregion } }
TaskHelper code:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Utils { /// <summary> /// Task Help Class Base Class /// </summary> public class TaskHelper { #region UI task private static LimitedTaskScheduler _UITask; /// <summary> /// UI task(4 Threads) /// </summary> public static LimitedTaskScheduler UITask { get { if (_UITask == null) _UITask = new LimitedTaskScheduler(4); return _UITask; } } #endregion #region Computing Tasks private static LimitedTaskScheduler _CalcTask; /// <summary> /// Computing Tasks(8 Threads) /// </summary> public static LimitedTaskScheduler CalcTask { get { if (_CalcTask == null) _CalcTask = new LimitedTaskScheduler(8); return _CalcTask; } } #endregion #region Network Request private static LimitedTaskScheduler _RequestTask; /// <summary> /// Network Request(32 Threads) /// </summary> public static LimitedTaskScheduler RequestTask { get { if (_RequestTask == null) _RequestTask = new LimitedTaskScheduler(32); return _RequestTask; } } #endregion #region Database Tasks private static LimitedTaskScheduler _DBTask; /// <summary> /// Database Tasks(32 Threads) /// </summary> public static LimitedTaskScheduler DBTask { get { if (_DBTask == null) _DBTask = new LimitedTaskScheduler(32); return _DBTask; } } #endregion #region IO task private static LimitedTaskScheduler _IOTask; /// <summary> /// IO task(8 Threads) /// </summary> public static LimitedTaskScheduler IOTask { get { if (_IOTask == null) _IOTask = new LimitedTaskScheduler(8); return _IOTask; } } #endregion #region Large Thread Pool Tasks private static LimitedTaskScheduler _LargeTask; /// <summary> /// Large Thread Pool Tasks(64 Threads) /// </summary> public static LimitedTaskScheduler LargeTask { get { if (_LargeTask == null) _LargeTask = new LimitedTaskScheduler(128); return _LargeTask; } } #endregion } }