到底該用多少線程?線程數、CPU核心數、本地計算時間、等待時間的關係 線程數 = CPU核心數 * ( 本地計算時間 + 等待時間 ) / 本地計算時間數據庫
下面是Task.Factory.StartNew和本身寫的TaskHelper.LargeTask.Run對比測試網絡
1、Task.Factory.StartNew 使用 TaskCreationOptions.LongRunning 參數ide
代碼:函數
private int n = 50000; //問題規模 private int t = 25; //等待時間 private int pageSize = 1000; //打印分頁 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); //等待時間 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 問題規模:{0} 等待時間:{1} 耗時:{2}秒】\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); //等待時間 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}線程 問題規模:{0} 等待時間:{1} 耗時:{2}秒】\r\n", n, t, stopwatch.Elapsed.TotalSeconds, TaskHelper.LargeTask.ThreadCount)); }); }); }
測試結果:測試
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線程 問題規模:50000 等待時間:25 耗時:10.5975181秒】
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 問題規模:50000 等待時間:25 耗時:8.2380754秒】
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線程 問題規模:50000 等待時間:25 耗時:10.4376939秒】
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 問題規模:50000 等待時間:25 耗時:9.2322552秒】this
測試結果說明:spa
個人電腦的CPU是i5-8265U,4核8線程
根據等待時間設置合適的線程數對TaskHelper.LargeTask.Run有利
使用TaskHelper.LargeTask.Run運行時的CPU佔用在5%如下,建立128個線程的瞬間CPU佔用達到30%,使用Task.Factory.StartNew運行時的CPU佔用接近100%
資源釋放狀況:Task.Factory.StartNew使用TaskCreationOptions.LongRunning參數運行完成後線程數當即釋放,句柄數未當即釋放,而TaskHelper.LargeTask.Run提供了手動釋放的方法能夠當即釋放線程數和句柄數,但須要手動調用才能釋放pwa
2、Task.Factory.StartNew 不使用 TaskCreationOptions.LongRunning 參數線程
代碼:code
private int n = 2000; //問題規模 private int t = 100; //等待時間 private int pageSize = 100; //打印分頁 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); //等待時間 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 問題規模:{0} 等待時間:{1} 耗時:{2}秒】\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); //等待時間 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}線程 問題規模:{0} 等待時間:{1} 耗時:{2}秒】\r\n", n, t, stopwatch.Elapsed.TotalSeconds, TaskHelper.LargeTask.ThreadCount)); }); }); }
測試結果:
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線程 問題規模:2000 等待時間:100 耗時:2.1529565秒】
0 2000 100 200 300 400 500 600 700 800 900 1900 1000 1100 1200 1300 1400 1500 1600 1700 1800
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:17.309869秒】
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線程 問題規模:2000 等待時間:100 耗時:2.143763秒】
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:8.8674353秒】
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:6.5490833秒】
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:5.1381533秒】
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:4.434294秒】
0 2000 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:4.329009秒】
2000 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:3.6231239秒】
2000 0 100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 1400 1500 1600 1700 1800 1900
【Task.Factory.StartNew 問題規模:2000 等待時間:100 耗時:3.6303149秒】
測試結論:
Task.Factory.StartNew在不使用TaskCreationOptions.LongRunning參數時,運行大量耗時任務,線程數增長緩慢,致使須要花費很長時間,若是線程池耗盡,或者線程池未耗盡但有大量耗時任務時,其它任務調用Task.Factory.StartNew會有延遲
我想了一天,多任務仍是不要共用線程池比較好,一個任務一個線程池,互不干擾,TaskHelper.LargeTask.Run就是按這個思路寫的,不知道可有問題
附:
LimitedTaskScheduler代碼:
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 外部方法 [DllImport("kernel32.dll", EntryPoint = "SetProcessWorkingSetSize")] public static extern int SetProcessWorkingSetSize(IntPtr process, int minSize, int maxSize); #endregion #region 變量屬性事件 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 構造函數 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 資源釋放 /// <summary> /// 資源釋放 /// 若是尚有任務在執行,則會在調用此方法的線程上引起System.Threading.ThreadAbortException,請使用Task.WaitAll等待任務執行完畢後,再調用該方法 /// </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 建立線程池 /// <summary> /// 建立線程池 /// </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 所有取消 /// <summary> /// 所有取消 /// </summary> public void CancelAll() { while (_tasks.TryTake(out _tempTask)) { } } #endregion } }
TaskHelper代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Utils { /// <summary> /// Task幫助類基類 /// </summary> public class TaskHelper { #region UI任務 private static LimitedTaskScheduler _UITask; /// <summary> /// UI任務(4個線程) /// </summary> public static LimitedTaskScheduler UITask { get { if (_UITask == null) _UITask = new LimitedTaskScheduler(4); return _UITask; } } #endregion #region 計算任務 private static LimitedTaskScheduler _CalcTask; /// <summary> /// 計算任務(8個線程) /// </summary> public static LimitedTaskScheduler CalcTask { get { if (_CalcTask == null) _CalcTask = new LimitedTaskScheduler(8); return _CalcTask; } } #endregion #region 網絡請求 private static LimitedTaskScheduler _RequestTask; /// <summary> /// 網絡請求(32個線程) /// </summary> public static LimitedTaskScheduler RequestTask { get { if (_RequestTask == null) _RequestTask = new LimitedTaskScheduler(32); return _RequestTask; } } #endregion #region 數據庫任務 private static LimitedTaskScheduler _DBTask; /// <summary> /// 數據庫任務(32個線程) /// </summary> public static LimitedTaskScheduler DBTask { get { if (_DBTask == null) _DBTask = new LimitedTaskScheduler(32); return _DBTask; } } #endregion #region IO任務 private static LimitedTaskScheduler _IOTask; /// <summary> /// IO任務(8個線程) /// </summary> public static LimitedTaskScheduler IOTask { get { if (_IOTask == null) _IOTask = new LimitedTaskScheduler(8); return _IOTask; } } #endregion #region 大線程池任務 private static LimitedTaskScheduler _LargeTask; /// <summary> /// 大線程池任務(64個線程) /// </summary> public static LimitedTaskScheduler LargeTask { get { if (_LargeTask == null) _LargeTask = new LimitedTaskScheduler(128); return _LargeTask; } } #endregion } }