什么是線程池?為什么要用線程池?怎么用線程池?
1. 什么是線程池?
??? ? ??.NET Framework的ThreadPool類提供一個線程池,該線程池可用于執行任務、發送工作項、處理異步 I/O、代表其他線程等待以及處理計時器。那么什么是線程池?線程池其實就是一個存放線程對象的“池子(pool)”,他提供了一些基本方法,如:設置pool中最小/最大線程數量、把要執行的方法排入隊列等等。ThreadPool是一個靜態類,因此可以直接使用,不用創建對象。
2. 為什么要用線程池?好處是什么?
??????? 微軟官網說法如下:許多應用程序創建大量處于睡眠狀態,等待事件發生的線程。還有許多線程可能會進入休眠狀態,這些線程只是為了定期喚醒以輪詢更改或更新的狀態信息。?線程池,使您可以通過由系統管理的工作線程池來更有效地使用線程。
??????? 說得簡單一點,每新建一個線程都需要占用內存空間和其他資源,而新建了那么多線程,有很多在休眠,或者在等待資源釋放;又有許多線程只是周期性的做一些小工作,如刷新數據等等,太浪費了,劃不來,實際編程中大量線程突發,然后在短時間內結束的情況很少見。于是,就提出了線程池的概念。線程池中的線程執行完指定的方法后并不會自動消除,而是以掛起狀態返回線程池,如果應用程序再次向線程池發出請求,那么處以掛起狀態的線程就會被激活并執行任務,而不會創建新線程,這就節約了很多開銷。只有當線程數達到最大線程數量,系統才會自動銷毀線程。因此,使用線程池可以避免大量的創建和銷毀的開支,具有更好的性能和穩定性,其次,開發人員把線程交給系統管理,可以集中精力處理其他任務。
3. 怎么使用線程池?
其實線程池使用起來很簡單,如下
a.設置線程池最大最小:
ThreadPool.SetMaxThreads (int workerThreads,int completionPortThreads)
設置可以同時處于活動狀態的線程池的請求數目。所有大于此數目的請求將保持排隊狀態,直到線程池線程變為可用。還可以設置最小線程數。
b.將任務添加進線程池:
ThreadPool.QueueUserWorkItem(new?WaitCallback(方法名));
或
ThreadPool.QueueUserWorkItem(new?WaitCallback(方法名), 參數);
舉個小例子,線程池中最多5個線程,執行一個方法60次,算5年總工資,如下:
如果不采用線程池,恐怕要開60線程異步執行Run()方法,空間資源之浪費,可見一斑。而現在我們最多用了5個線程,1秒內即可執行完畢,效率、性能都很好。
----------------------------------------------------------------------------------------------------------------------------------
C#線程池ThreadPool.QueueUserWorkItem接收線程執行的方法返回值
最近在項目中需要用到多線程,考慮了一番,選擇了ThreadPool,我的需求是要拿到線程執行方法的返回值,
但是ThreadPool.QueueUserWorkItem的回調方法默認是沒有返回值的,搜了搜,都是簡單介紹ThreadPool.QueueUserWorkItem的各種
用法,只能自己想辦法了。
回調方法不帶返回值,迂回一下,回調方法用對象的方法,返回值放在對象的屬性中,在對象方法執行時將需要的返回值賦值給對應屬性。
等所有線程執行完,循環對象列表,取回返回值,然后想怎么處理返回值就OK了。上代碼:
封裝對象:
1 using System;
2 using System.Threading;
3 public class ThreadReturnData
4 {
5 public ManualResetEvent manual;
6 public string res;
7
8 public void ReturnThreadData(object obj)
9 {
10 //線程耗時操作方法
11 res = DoSomething(obj);
12 manual.Set();
13 }
14 }
多線程調用:
1 List<ThreadReturnData> testList = new List<ThreadReturnData>();
2 IList<ManualResetEvent> arrManual = new List<ManualResetEvent>();
3 for (int i = 0; i < i; i++)
4 {
5 ThreadReturnData temp = new ThreadReturnData();
6 temp.manual = new ManualResetEvent(false);
7 arrManual.Add(temp.manual);
8 ThreadPool.QueueUserWorkItem(new WaitCallback(temp.ReturnThreadData), i);
9 testList.Add(temp);
10 }
11 }
12 if (arrManual.Count > 0)
13 {
14 ////等待所有線程執行完
15 WaitHandle.WaitAll(arrManual.ToArray());
16 }
17 foreach (ThreadReturnData d in testList)
18 {
19 d.res;
20 //todo
21 }
-------------------------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------------------------------------------
https://blog.csdn.net/zhaoguanghui2012/article/details/52910035
一、CLR線程池
管理線程開銷最好的方式:
- 盡量少的創建線程并且能將線程反復利用(線程池初始化時沒有線程,有程序請求線程則創建線程);
- 最好不要銷毀而是掛起線程達到避免性能損失(線程池創建的線程完成任務后以掛起狀態回到線程池中,等待下次請求);
- 通過一個技術達到讓應用程序一個個執行工作,類似于一個隊列(多個應用程序請求線程池,線程池會將各個應用程序排隊處理);
- 如果某一線程長時間掛起而不工作的話,需要徹底銷毀并且釋放資源(線程池自動監控長時間不工作的線程,自動銷毀);
- 如果線程不夠用的話能夠創建線程,并且用戶可以自己定制最大線程創建的數量(當隊列過長,線程池里的線程不夠用時,線程池不會坐視不理);
微軟早就替我們想到了,為我們實現了線程池。
CLR線程池并不會在CLR初始化時立即建立線程,而是在應用程序要創建線程來運行任務時,線程池才初始化一個線程。
線程池初始化時是沒有線程的,線程池里的。線程的初始化與其他線程一樣,但是在完成任務以后,該線程不會自行銷毀,而是以掛起的狀態返回到線程池。直到應用程序再次向線程池發出請求時,線程池里掛起的線程就會再度激活執行任務。
這樣既節省了建立線程所造成的性能損耗,也可以讓多個任務反復重用同一線程,從而在應用程序生存期內節約大量開銷。
通過CLR線程池所建立的線程總是默認為后臺線程,優先級數為ThreadPriority.Normal。
二、工作者線程與I/O線程
CLR線程池分為工作者線程(workerThreads)與I/O線程(completionPortThreads)兩種:
- 工作者線程是主要用作管理CLR內部對象的運作,通常用于計算密集的任務。
- I/O(Input/Output)線程主要用于與外部系統交互信息,如輸入輸出,CPU僅需在任務開始的時候,將任務的參數傳遞給設備,然后啟動硬件設備即可。等任務完成的時候,CPU收到一個通知,一般來說是一個硬件的中斷信號,此時CPU繼續后繼的處理工作。在處理過程中,CPU是不必完全參與處理過程的,如果正在運行的線程不交出CPU的控制權,那么線程也只能處于等待狀態,即使操作系統將當前的CPU調度給其他線程,此時線程所占用的空間還是被占用,而并沒有CPU處理這個線程,可能出現線程資源浪費的問題。如果這是一個網絡服務程序,每一個網絡連接都使用一個線程管理,可能出現大量線程都在等待網絡通信,隨著網絡連接的不斷增加,處于等待狀態的線程將會很消耗盡所有的內存資源。可以考慮使用線程池解決這個問題。
線程池的最大值一般默認為1000、2000。當大于此數目的請求時,將保持排隊狀態,直到線程池里有線程可用。
使用CLR線程池的工作者線程一般有兩種方式:
- 通過ThreadPool.QueueUserWorkItem()方法;
- 通過委托;
要注意,不論是通過ThreadPool.QueueUserWorkItem()還是委托,調用的都是線程池里的線程。
三、ThreadPool類常用方法
通過以下兩個方法可以讀取和設置CLR線程池中工作者線程與I/O線程的最大線程數。
- ThreadPool.GetMax(out in workerThreads,out int completionPortThreads);
- ThreadPool.SetMax(int workerThreads,int completionPortThreads);
若想測試線程池中有多少線程正在投入使用,可以通過ThreadPool.GetAvailableThreads(out in workThreads,out int conoletionPortThreads)方法。
方法 | 說明 |
GetAvailableThreads | 剩余空閑線程數 |
GetMaxThreads | 最多可用線程數,所有大于此數目的請求將保持排隊狀態,直到線程池線程變為可用 |
GetMinThreads | 檢索線程池在新請求預測中維護的空閑線程數。 |
QueueUserWorkItem | 啟動線程池里得一個線程(隊列的方式,如線程池暫時沒空閑線程,則進入隊列排隊) |
SetMaxThreads | 設置線程池中的最大線程數 |
SetMinThreads | 設置線程池最少需要保留的線程數 |
class Program{static void Main(string[] args){int i = 0;int j = 0;//前面是輔助(也就是所謂的工作者)線程,后面是I/O線程ThreadPool.GetMaxThreads(out i, out j);Console.WriteLine(i.ToString() + " " + j.ToString()); //默認都是1000//獲取空閑線程,由于現在沒有使用異步線程,所以為空ThreadPool.GetAvailableThreads(out i, out j);Console.WriteLine(i.ToString() + " " + j.ToString()); //默認都是1000
Console.ReadKey();}}
四、各種調用線程池線程的方法
1、通過QueueUserWorkItem啟動工作者線程
ThreadPool線程池中有兩個重載的靜態方法可以直接啟動工作者線程:
- ThreadPool.QueueUserWorkItem(waitCallback);
- ThreadPool.QueueUserWorkItem(waitCallback,Object);
先把WaitCallback委托指向一個帶有Object參數的無返回值方法,再使用ThreadPool.QueueUserWorkItem(WaitCallback)就可以一步啟動此方法,此時異步方法的參數被視為null。
下面來試下用QueueUserWorkItem啟動線程池里的一個線程。注意哦,由于是一直存在于線程池,所以不用new Thread()。
class Program{static void Main(string[] args){//工作者線程最大數目,I/O線程的最大數目ThreadPool.SetMaxThreads(1000, 1000); //啟動工作者線程ThreadPool.QueueUserWorkItem(new WaitCallback(RunWorkerThread));Console.ReadKey();}static void RunWorkerThread(object state){Console.WriteLine("RunWorkerThread開始工作");Console.WriteLine("工作者線程啟動成功!");}}
輸出:

使用第二個重載方法ThreadPool.QueueUserWorkItem(WaitCallback,object)方法可以把object對象作為參數傳送到回調函數中。
class Program{static void Main(string[] args){Person p = new Person(1,"劉備");//啟動工作者線程ThreadPool.QueueUserWorkItem(new WaitCallback(RunWorkerThread), p);Console.ReadKey();}static void RunWorkerThread(object obj){Thread.Sleep(200);Console.WriteLine("線程池線程開始!");Person p = obj as Person;Console.WriteLine(p.Name);}}public class Person{public Person(int id,string name) { Id = id; Name = name; }public int Id { get; set; }public string Name { get; set; }}
輸出結果如下:

通過ThreadPool.QueueUserWork啟動工作者線程非常方便,但是WaitCallback委托指向的必須是一個帶有object參數的無返回值方法。所以這個方法啟動的工作者線程僅僅適合于帶單個參數和無返回值的情況。
那么如果要傳遞多個參數和要有返回值又應該怎么辦呢?那就只有通過委托了。
2、BeginInvoke與EndInvoke委托異步調用線程
異步調用委托的步驟如下:
- 建立一個委托對象,通過IAsyncResult?BeginInvoke(string name,AsyncCallback callback,object state)異步調用委托方法,BeginInvoke方法除最后的兩個參數外,其他參數都是與方法參數相對應的。
- 利用EndInvoke(IAsyncResult--上一步BeginInvoke返回的對象)方法就可以結束異步操作,獲取委托的運行結果。
class Program{//除了最后兩個參數,前面的都是你可定義的delegate string MyDelegate(string name,int age);static void Main(string[] args){//建立委托MyDelegate myDelegate = new MyDelegate(GetString);//異步調用委托,除最后兩個參數外,前面的參數都可以傳進去IAsyncResult result = myDelegate.BeginInvoke("劉備",22, null, null); //IAsynResult還能輪詢判斷,功能不弱Console.WriteLine("主線程繼續工作!");//調用EndInvoke(IAsyncResult)獲取運行結果,一旦調用了EndInvoke,即使結果還沒來得及返回,主線程也阻塞等待了//注意獲取返回值的方式string data = myDelegate.EndInvoke(result);Console.WriteLine(data);Console.ReadKey();}static string GetString(string name, int age){Console.WriteLine("我是不是線程池線程" + Thread.CurrentThread.IsThreadPoolThread);Thread.Sleep(2000);return string.Format("我是{0},今年{1}歲!",name,age);}}
輸出如下:

這種方法有一個缺點,就是不知道異步操作什么時候執行完,什么時候開始調用EndInvoke,因為一旦EndInvoke主線程就會處于阻塞等待狀態。
3、IAsyncResult輪詢
為了克服上面提到的缺點,此時可以好好利用IAsyncResult提高主線程的工作性能,IAsyncResult有如下成員。
public interface IAsyncResult
{object AsyncState {get;} //獲取用戶定義的對象,它限定或包含關于異步操作的信息。WailHandle AsyncWaitHandle {get;} //獲取用于等待異步操作完成的 WaitHandle。bool CompletedSynchronously {get;} //獲取異步操作是否同步完成的指示。bool IsCompleted {get;} //獲取異步操作是否已完成的指示。
}
示例如下:
class Program{delegate string MyDelegate(string name,int age);static void Main(string[] args){MyDelegate myDelegate = new MyDelegate(GetString);IAsyncResult result = myDelegate.BeginInvoke("劉備",22, null, null);Console.WriteLine("主線程繼續工作!");//比上個例子,只是利用多了一個IsCompleted屬性,來判斷異步線程是否完成 while (!result.IsCompleted){Thread.Sleep(500); Console.WriteLine("異步線程還沒完成,主線程干其他事!");}string data = myDelegate.EndInvoke(result);Console.WriteLine(data);Console.ReadKey();}static string GetString(string name, int age){Thread.Sleep(2000);return string.Format("我是{0},今年{1}歲!",name,age);}}
輸出如下:

以上例子,除了IsCompleted屬性外,還可以使用AsyncWaitHandle如下3個方法實現同樣輪詢判斷效果:
- WaitOne:判斷單個異步線程是否完成;
- WaitAny:判斷是否異步線程是否有指定數量個已完成;
- WaitAll:判斷是否所有的異步線程已完成;
WaitOne:
//比上個例子,判斷條件由IsCompleted屬性換成了AsyncWaitHandle,僅此而已while (!result.AsyncWaitHandle.WaitOne(200)){Console.WriteLine("異步線程沒完,主線程繼續干活!");}
WaitAny:
//是否完成了指定數量WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle };while (WaitHandle.WaitAny(waitHandleList, 200) > 0){Console.WriteLine("異步線程完成數未大于0,主線程繼續甘其他事!");}
WaitAll:
WaitHandle[] waitHandleList = new WaitHandle[] { result.AsyncWaitHandle };//是否全部異步線程完成while (!WaitHandle.WaitAll(waitHandleList, 200)){Console.WriteLine("異步線程未全部完成,主線程繼續干其他事!");}
4、IAsyncResult回調函數
使用輪詢方式來檢測異步方法的狀態非常麻煩,而且影響了主線程,效率不高。能不能異步線程完成了就直接調用實現定義好的處理函數呢?
有,還是強大的IAsyncResult對象。
class Program{delegate string MyDelegate(string name, int age);static void Main(string[] args){//建立委托MyDelegate myDelegate = new MyDelegate(GetString);//倒數第二個參數,委托中綁定了完成后的回調方法IAsyncResult result1 = myDelegate.BeginInvoke("劉備",23, new AsyncCallback(Completed), null);//主線程可以繼續工作而不需要等待Console.WriteLine("我是主線程,我干我的活,不再理你!");Thread.Sleep(5000);//Console.ReadKey();
}static string GetString(string name, int age){Thread.CurrentThread.Name = "異步線程";//注意,如果不設置為前臺線程,則主線程完成后就直接卸載程序了//Thread.CurrentThread.IsBackground = false;Thread.Sleep(2000);return string.Format("我是{0},今年{1}歲!", name, age);}//供異步線程完成回調的方法static void Completed(IAsyncResult result){//獲取委托對象,調用EndInvoke方法獲取運行結果AsyncResult _result = (AsyncResult)result;MyDelegate myDelegaate = (MyDelegate)_result.AsyncDelegate;//獲得參數string data = myDelegaate.EndInvoke(_result);Console.WriteLine(data);//異步線程執行完畢Console.WriteLine("異步線程完成咯!");Console.WriteLine("回調函數也是由" + Thread.CurrentThread.Name + "調用的!");}}
輸出如下:

注意:
- 回調函數依然是在輔助線程中執行的,這樣就不會影響主線程的運行。
- 線程池的線程默認是后臺線程。但是如果主線程比輔助線程優先完成,那么程序已經卸載,回調函數未必會執行。如果不希望丟失回調函數中的操作,要么把異步線程設為前臺線程,要么確保主線程將比輔助線程遲完成。
到目前為止,BeginInvoke("劉備",23, new AsyncCallback(Completed), null)還有最后一個參數沒用過的。那么最后一個參數是用來干什么?傳參:

namespace 控制臺___學習測試
{class Program{delegate string MyDelegate(string name, int age);static void Main(string[] args){Person p = new Person(2,"關羽");//建立委托MyDelegate myDelegate = new MyDelegate(GetString);//最后一個參數的作用,原來是用來傳參的IAsyncResult result1 = myDelegate.BeginInvoke("劉備", 23, new AsyncCallback(Completed), p);//主線程可以繼續工作而不需要等待Console.WriteLine("我是主線程,我干我的活,不再理你!");Console.ReadKey();}static string GetString(string name, int age){Thread.CurrentThread.Name = "異步線程";//注意,如果不設置為前臺線程,則主線程完成后就直接卸載程序了Thread.CurrentThread.IsBackground = false;Thread.Sleep(2000);return string.Format("我是{0},今年{1}歲!", name, age);}//供異步線程完成回調的方法static void Completed(IAsyncResult result){//獲取委托對象,調用EndInvoke方法獲取運行結果AsyncResult _result = (AsyncResult)result;MyDelegate myDelegaate = (MyDelegate)_result.AsyncDelegate;//獲得參數string data = myDelegaate.EndInvoke(_result);Console.WriteLine(data);Person p = result.AsyncState as Person;Console.WriteLine("傳過來的參數是:" + p.Name);//異步線程執行完畢Console.WriteLine("異步線程完成咯!");Console.WriteLine("回調函數也是由" + Thread.CurrentThread.Name + "調用的!");}}public class Person{public Person(int id, string name){Id = id;Name = name;}public int Id{get;set;}public string Name{get;set;}}
}

輸出如下:
