摘要:
系列文章,從一個基本的代碼說起,逐步探索 ThreadPool 的奧妙。
首先,看看線程池的樣子:
從上圖看出,線程池維護1個至n個線程,操作系統從請求隊列中提取請求分配個線程池中的適合線程處理。
先寫下如下的代碼:
using System;
using System.Threading;
public class ThreadBase
{
public static void Main ( ) {
System.Threading.WaitCallback waitCallback = new WaitCallback ( MyThreadWork );
ThreadPool.QueueUserWorkItem ( waitCallback, "第一個線程" );
ThreadPool.QueueUserWorkItem ( waitCallback, "第二個線程" );
ThreadPool.QueueUserWorkItem ( waitCallback, "第三個線程" );
ThreadPool.QueueUserWorkItem ( waitCallback, "第四個線程" );
Console.ReadLine ( );
}
public static void MyThreadWork ( object state ) {
Console.WriteLine ( "線程現在開始啓動…… {0}",(string)state );
Thread.Sleep ( 10000 );
Console.WriteLine ( "運行結束…… {0}",( string ) state );
}
}
分析上面的代碼:
一、首先定義了一個 System.Threading.WaitCallback 對象 waitCallback。
WaitCallback 是一個委託,表示線程池線程要執行的回調方法,它的原型如下:
[ComVisibleAttribute(true)]
public delegate void WaitCallback (Object state)
1、這裏有一個 Callback 機制的問題,所謂的Callback 簡單的理解就是由操作系統調用的函數,我們自己寫的程序不需要調用。就像有個修理工到家裏搞修理,你只需要告訴他鉗子、螺絲刀、膠布等修理工具在什麼地方就行,而不要管他在什麼時候、在什麼地方怎樣使用這些修理工具。
2、WaitCallback 的參數" Object state",這個參數包含回調方法要使用的信息的對象。在接下來的情況中我再說。
既然 WaitCallback 委託的原型如此,那麼我們就申明一個跟它的樣子差不多的函數,這個函數就是要線程做的事情。
public static void MyThreadWork ( object state )
這裏函數中多了一個 "static",這是因爲Main的關係(更大一點就是因爲C#語言機制的問題),如果WaitCallback 的對象不是在靜態(static)方法中,這個static 是不需要的。
二、接着就是要執行的方法放入線程池中,以便操作系統執行。
我這裏放置了四個方法要操作系統執行:
ThreadPool.QueueUserWorkItem ( waitCallback, "第一個線程" );
ThreadPool.QueueUserWorkItem ( waitCallback, "第二個線程" );
ThreadPool.QueueUserWorkItem ( waitCallback, "第三個線程" );
ThreadPool.QueueUserWorkItem ( waitCallback, "第四個線程" );
這裏,我放置到線程池中的操作是一樣的,當然也可以不一樣,接下來說。
三、最後阻塞主線程,等待線程池中的線程執行
Console.ReadLine ( );
如果忽略掉這個代碼,則有可能看不到任何輸出
好了,這是主線程做的事情了,接下來看看線程池中的線程做的事情。
這個很簡單,就是將線程的參數輸出,然後線程睡眠(sleep)一段時間,最後輸出線程結束的信息。
在上次的基礎上,我需要做以下的事情:
1、要傳遞給線程的參數更加複雜;
2、線程要處理的工作更加多樣;
3、在線程中我要改變主線程傳入的參數,並告訴主程序。
好的,第一個問題,因爲 WaitCallback 委託的原型決定了參數只能有一個 ( Object state ),那沒有辦法,我們只能將多個參數封裝到一個Object 中,也就是 class 中。
第二個問題,要處理這個問題也很簡單,就是再定義一個 WaitCallback 委託的對象,將它作爲參數傳遞給ThreadPool.QueueUserWorkItem ()方法。這個對象可以在線程裏面處理另外一類的工作。
代碼如下:
using System;
using System.Threading;
public class App
{
public static void Main ( ) {
WaitCallback waitCallback = new WaitCallback ( MyThreadWork );
WaitCallback waitCallback2= new WaitCallback ( MyThreadWork2 ); // 增加線程工作類型
ThreadPool.QueueUserWorkItem ( waitCallback, "第一個線程" );
ThreadPool.QueueUserWorkItem ( waitCallback, "第二個線程" );
MyState myState = new MyState ( "第三個線程",100); // 增加自定義的線程參數類型
ThreadPool.QueueUserWorkItem ( waitCallback2, myState );
ThreadPool.QueueUserWorkItem ( waitCallback2, new MyState("第四個線程",2) );
Console.WriteLine ( "MyState 的 Number 值爲: {0}", myState.Number ); // 讀取線程改變後的 MyState
Console.ReadLine ( );
}
public static void MyThreadWork ( object state ) {
Console.WriteLine ( "MyThreadWork 開始啓動 …… {0}", ( string ) state );
Thread.Sleep ( 10000 );
Console.WriteLine ( "運行結束…… {0}", ( string ) state );
}
// use MyState class
public static void MyThreadWork2 ( object state ) {
Console.WriteLine ( "MyThreadWork2 開始啓動…… {0},{1}", ( ( MyState ) state ).Info, ( ( MyState ) state ).Number );
Thread.Sleep ( 10000 );
( ( MyState ) state ).Number += 1; // 將 state的 Number 加 1
Console.WriteLine ( "運行結束…… {0},{1}", ( ( MyState ) state ).Info, ( ( MyState ) state ).Number );
}
}
public class MyState
{
private string info;
private int number;
public MyState ( string info, int number ) {
this.info = info;
this.number = number;
}
public string Info {
get {
return this.info;
}
set {
this.info = value;
}
}
public int Number {
get {
return this.number;
}
set {
this.number = value;
}
}
}
在代碼裏面,我嘗試對三個問題都進行解決,但是,很遺憾,上面的代碼只是解決了第1、2個問題。
雖然,我的MyThreadWork2 () 線程嘗試對 MyState 的 Number 進行加1操作,但是,主線程的輸出仍然可能是 100,如下所示:
MyState 的 Number 值爲: 100
MyThreadWork 開始啓動 …… 第一個線程
MyThreadWork 開始啓動 …… 第二個線程
MyThreadWork2 開始啓動…… 第三個線程,100
MyThreadWork2 開始啓動…… 第四個線程,2
運行結束…… 第一個線程
運行結束…… 第二個線程
運行結束…… 第三個線程,101
運行結束…… 第四個線程,3
光從代碼看,我們的輸出 MyState 信息的代碼應該是後面執行,想不到它居然在線程啓動之前就執行了!
呵呵,這就是多線程!
看樣子,我需要控制我的主線程——等所有的線程池中的線程都執行完成後,才接着執行主線程中輸出 MyState 信息的代碼。