本片文章的議題是有關於傳遞引數到執行緒的幾種方法。
首先我們要知道什麼是執行緒,什麼時候要用到執行緒,如何去使用執行緒,如何更好的利用執行緒來完成工作。
執行緒是程式可執行片段的最小單元,是組成執行時程式的基本單元,一個程式有至少一個執行緒組成。一般在並行處理等待事件的時候要用到執行緒,如等待網路響應,等待I/O通訊,後臺事務處理等情況。使用執行緒其實很簡單,在.net框架下面你首先要定義一個函式來完成一些工作,然後例項化一個執行緒物件Thread thrd = new Thread(new ThreadStart(執行緒函式)); 其中ThreadStart是一個不帶引數的函式委託。最後使用thrd.Start()就可以啟動執行緒了。當然這只是一個很簡單的例子,實際中使用執行緒會有很多的問題要解決,比如傳遞引數到執行緒中,等待執行緒返回,如何同步執行緒進行同一資源訪問,如何防止死鎖和競爭條件,如何有效的利用執行緒池,如何提供執行緒效率等問題。本文在這裡將只對傳遞引數到執行緒進行探討。
在上面舉例中我們看到執行緒例項化的引數是一個不帶任何引數的函式委託,那麼就證明了我們不可能通過這樣一個委託傳遞引數到執行緒內部。那麼我們該怎麼做呢?就我目前所知有三種方法可以實現:
- 1. 利用執行緒實現類,將呼叫引數定義成屬性的方式來操作執行緒引數;
- 2. 利用ParameterizedThreadStart委託來傳遞輸入引數;
- 3. 利用執行緒池來實現引數傳入。
1. 建立執行緒實現類,這種方式有個最大的優點就是可以通過執行緒返回多個返回值
假設存在在一個列印功能的執行緒,通過主函式傳入列印的行,列和字元資料,並返回列印的字元總數。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
class ThreadOutput { int _rowCount = 0; int _colCount = 0; char _char = '*'; int _ret = 0; /// <summary> /// 輸入引數 /// </summary> public int RowCount { set { _rowCount = value; } } public int ColCount { set { _colCount = value; } } public char Character { set { _char = value; } } /// <summary> /// 輸出引數 /// </summary> public int RetVal { get { return _ret; } } public void Output() { for (int row = 0; row < _rowCount; row++) { for (int col = 0; col < _colCount; col++) { Console.Write("{0} ", _char); _ret++; } Console.Write("\n"); } } ThreadOutput to1 = new ThreadOutput(); to1.RowCount = 10; to1.ColCount = 20; Thread thrd = new Thread(new ThreadStart(to1.Output)); // 設定為後臺執行緒,主要是為不影響主執行緒的結束 thrd.IsBackground = true; thrd.Start(); |
最後要注意的是由於執行緒實現類是通過屬性來傳遞數值的,那麼在屬性訪問器中要進行執行緒同步,否則取得的值可能不正確。
2. 利用ParameterizedThreadStart委託來傳遞輸入引數
ParameterizedThreadStart委託是.Net2.0中才有的。該委託提供來在啟動執行緒時傳遞一個object引數到執行緒中。這種方式使用起來比較簡單,但是由於需要對object物件進行型別轉換,所以存在型別不一致的隱患。
3. 利用執行緒池來實現引數傳入
什麼是執行緒池,執行緒池是系統提供一個存放執行緒的容器,進入執行緒池後的執行緒控制權由系統掌握。利用執行緒池的好處是我們無需為執行緒存在大量空閒時間而去思考乾點別的什麼,適合於常規性的事務處理。在.Net中執行緒池是一個static類,所以我們需要通過ThreadPool來呼叫相關的函式。其中向執行緒池中加入執行緒的函式就是 ThreadPool.QueueUserWorkItem(new WaitCallBack(), object obj) 。這個函式有個WaitCallBack 的委託,[ComVisibleAttribute(true)]
public delegate void WaitCallback(Object state)
通過這個委託我們也可以傳遞引數到執行緒中。
其實還有一種方法可以傳遞引數到執行緒中,那就是通過回撥函式來實現,只不過這種方法本質上和第一種通過類的資料成員來傳遞引數的機制一樣。所以就不再細說來!
具體的實現可以參考下面的原始碼:
全部原始碼如下:(vs 2005編譯通過)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 |
using System; using System.Collections.Generic; using System.Text; using System.Threading; namespace UsingThread { struct RowCol { public int row; public int col; }; class ThreadOutput { // 建立等待時間 static public ManualResetEvent prompt = new ManualResetEvent(false); int _rowCount = 0; int _colCount = 0; char _char = '*'; int _ret = 0; /// <summary> /// 輸入引數 /// </summary> public int RowCount { set { _rowCount = value; } } public int ColCount { set { _colCount = value; } } public char Character { set { _char = value; } } /// <summary> /// 輸出引數 /// </summary> public int RetVal { get { return _ret; } } public void Output() { for (int row = 0; row < _rowCount; row++) { for (int col = 0; col < _colCount; col++) { Console.Write("{0} ", _char); _ret++; } Console.Write("\n"); } // 啟用事件 prompt.Set(); } public void Output(Object rc) { RowCol rowCol = (RowCol)rc; for (int i = 0; i < rowCol.row; i++) { for (int j = 0; j < rowCol.col; j++) Console.Write("{0} ", _char); Console.Write("\n"); } } } class Program { static void Main(string[] args) { ThreadOutput to1 = new ThreadOutput(); to1.RowCount = 10; to1.ColCount = 20; Thread thrd = new Thread(new ThreadStart(to1.Output)); // 設定為後臺執行緒,主要是為不影響主執行緒的結束 thrd.IsBackground = true; thrd.Start(); // 建立事件等待 ThreadOutput.prompt.WaitOne(); Console.WriteLine("{0}", to1.RetVal); ThreadOutput to2 = new ThreadOutput(); to2.RowCount = 5; to2.ColCount = 18; to2.Character = '^'; Thread thrds = new Thread(new ThreadStart(to2.Output)); thrds.IsBackground = true; thrds.Start(); thrds.Join(); Console.WriteLine("{0}", to2.RetVal); // 傳遞引數給執行緒的另一種實現 RowCol rc = new RowCol(); rc.row = 12; rc.col = 13; to1.Character = '@'; if (ThreadPool.QueueUserWorkItem(new WaitCallback(to1.Output), rc)) Console.WriteLine("Insert Pool is success!"); else Console.WriteLine("Insert Pool is failed!"); Thread.Sleep(1000); // 使用新的ThreadStart委託來傳遞引數 rc.col = 19; to2.Character = '#'; Thread thrdt = new Thread(new ParameterizedThreadStart(to2.Output)); thrdt.Start(rc); thrdt.Join(); } } } |