線程池是一種多線程處理形式,處理過程中將任務添加到隊列,然后在創建線程后自動啟動這些任務。線程池線程都是后台線程。每個線程都使用默認的堆棧大小,以默認的優先級運行。
[C#線程池]
場景:以下是C# winform寫的一個線程池示例程序。窗體上,分別拖放一個開始和停止按鈕,單擊開始按鈕,for尋覓模擬7000個任務隊列,在線程池運行過程中,可以點擊停止按鈕,來結束線程池所有任務,這里借助CancellationTokenSource對象,來實現線程池的停止。
開始前,請大家看以下代碼,有什么問題?這里以C# Winform的形式,向大家展現以下代碼:
1、第一種:這是開始按鈕單擊事件的代碼
btnStart.Enabled = false; isStop = false; listCancels.Clear();//清空 cancelToken.Dispose(); cancelToken = new CancellationTokenSource(); System.Threading.ThreadPool.SetMaxThreads(4, 2); System.Threading.ThreadPool.SetMinThreads(2, 1); for (int i = 0; i < 7000; i++) { System.Threading.ThreadPool.QueueUserWorkItem((state) => { while (true) { lock (locker) { if (isStop) { break; }; if (cancelToken.IsCancellationRequested) { break; } } int works = 0, maxwork = 0; int port = 0, maxport = 0; System.Threading.ThreadPool.GetAvailableThreads(out works, out port); System.Threading.ThreadPool.GetMaxThreads(out maxwork, out maxport); lblStatus.BeginInvoke(new Action(() => { lblStatus.Text = System.Threading.Thread.CurrentThread.ManagedThreadId + ":" + Guid.NewGuid(); })); System.Threading.Thread.Sleep(100); } }); }
2、第二種:看出有何不同了嗎,好像差不多,都很正常對吧
btnStart.Enabled = false; isStop = false; listCancels.Clear();//清空 cancelToken.Dispose(); cancelToken = new CancellationTokenSource(); System.Threading.ThreadPool.SetMaxThreads(4, 2); for (int i = 0; i < 7000; i++) { System.Threading.ThreadPool.QueueUserWorkItem((state) => { while (true) { lock (locker) { if (isStop) { break; }; if (cancelToken.IsCancellationRequested) { break; } } int works = 0, maxwork = 0; int port = 0, maxport = 0; System.Threading.ThreadPool.GetAvailableThreads(out works, out port); System.Threading.ThreadPool.GetMaxThreads(out maxwork, out maxport); lblStatus.BeginInvoke(new Action(() => { lblStatus.Text = System.Threading.Thread.CurrentThread.ManagedThreadId + ":" + Guid.NewGuid(); })); System.Threading.Thread.Sleep(100); } }); }
3、第三種:
btnStart.Enabled = false; isStop = false; listCancels.Clear();//清空 cancelToken.Dispose(); cancelToken = new CancellationTokenSource(); System.Threading.ThreadPool.SetMinThreads(2, 1); System.Threading.ThreadPool.SetMaxThreads(4, 2); for (int i = 0; i < 7000; i++) { System.Threading.ThreadPool.QueueUserWorkItem((state) => { while (true) { lock (locker) { if (isStop) { break; }; if (cancelToken.IsCancellationRequested) { break; } } int works = 0, maxwork = 0; int port = 0, maxport = 0; System.Threading.ThreadPool.GetAvailableThreads(out works, out port); System.Threading.ThreadPool.GetMaxThreads(out maxwork, out maxport); lblStatus.BeginInvoke(new Action(() => { lblStatus.Text = System.Threading.Thread.CurrentThread.ManagedThreadId + ":" + Guid.NewGuid(); })); System.Threading.Thread.Sleep(100); } }); }
如果沒有看出以上三段代碼有何不同,那就一定要看看下面的內容。
接下來,我們借助操作系統的任務管理器,來及時監測Winform進程所進行的線程數。
如果,是第一種寫法,我們來看看:
結果,並不像我們預想的那樣,即使如此,線程數還在一路飆升,不一會兒竟達到兩百多個線程,這是為何?
第二種,只不過是比第一種少些了一行System.Threading.ThreadPool.SetMinThreads(2, 1);的代碼,結果,
好像沒有什么變化,線程池仍然一路飆升,直到UI線程出現假死。
就在我寫這兩段博客的瞬間,看看任務管理器的情況吧:
什么,線程數601?暫不說它是否精確,但此刻,程序是很不正常的在運行。
第三種,是在第一種基礎上,調整了兩行代碼的順序,恰是這兩行代碼,讓我們正視了之前線程池誤區。也改變了結果。以第三種方式,我將程序運行6個多小時,一夜不關,程序依然運行平穩,線程數依然保持在較穩的水平。
原來,順序真的很重要。經常看到網上有關C#線程池用法的博客,當我們看到,上來都寫System.Threading.ThreadPool.SetMaxThreads(4, 2);這行代碼時,我們切記要注意了。當任務量小的時候,這種問題並不明顯,依然能很好的完成工作。但是,像我以上的例子,龐大任務隊列加入線程池,每個任務又以漫長的運行周期在運行。那把上面那個例子,搬上去看看,這種寫法必死,切記。
使用C#線程池的時候,一定要先設置 System.Threading.ThreadPool.SetMinThreads(2, 1);,然后再設置 System.Threading.ThreadPool.SetMaxThreads(4, 2);,這才是C#線程池正確的用法。
最后給大家介紹一個關鍵詞volatile,不知道的朋友,可以了解下。想要了解更多多線程池方面的信息,請訪問http://www.lichaoqiang.com/