C#中執行緒的委託
很多時候寫windows程式都需要結合多執行緒,在C#中用如下得程式碼來建立並啟動一個新的執行緒。
Thread thread = new Thread(new ThreadStart(ThreadProc));//例項化一個執行緒 thread.IsBackground = true;//將執行緒改為後臺執行緒 thread.Start();//開啟執行緒
但是很多時候,在新的執行緒中,我們需要與UI(Windows窗體設計器使用者介面)進行互動,在C#中不允許直接這樣做。可以參考MSDN中的描述。
“Windows 窗體”使用單執行緒單元 (STA) 模型,因為“Windows 窗體”基於本機Win32視窗,而Win32視窗從本質上而言是單元執行緒。STA模型意味著可以在任何執行緒上建立視窗,但視窗一旦建立後就不能切換執行緒,並且對它的所有函式呼叫都必須在其建立執行緒上發生。除了Windows窗體之外,.NET Framework 中的類使用自由執行緒模型。
STA模型要求需從控制元件的非建立執行緒呼叫的控制元件上的任何方法必須被封送到(在其上執行)該控制元件的建立執行緒。基類Control為此目的提供了若干方法(Invoke、BeginInvoke 和 EndInvoke)。Invoke生成同步方法呼叫;BeginInvoke生成非同步方法呼叫。
Windows窗體中的控制元件被繫結到特定的執行緒,不具備執行緒安全性。因此,如果從另一個執行緒呼叫控制元件的方法,那麼必須使用控制元件的一個Invoke方法來將呼叫封送到適當的執行緒。
正如所看到的,必須呼叫Invoke方法,而BeginInvoke可以認為是Invoke的非同步版本。呼叫方法如下:
public delegate void OutDelegate(string text); public void OutText(string text) { txt.AppendText(text); txt.AppendText( "\t\n" ); } OutDelegate outdelegate = new OutDelegate( OutText ); this.BeginInvoke(outdelegate, new object[]{text});
如果需要在另外一個執行緒裡面對UI進行操作,需要一個類似OutText的函式,還需要一個該函式的委託delegate,當然,這裡展示的是自定義的。
該屬性可用於確定是否必須呼叫 Invoke 方法,當不知道什麼執行緒擁有控制元件時這很有用。
也就是說通過判斷InvokeRequired可以知道是否需要用委託來呼叫當前控制元件的一些方法,如此可以把OutText函式修改一下:
public delegate void OutDelegate(string text); public void OutText(string text) { if( txt.InvokeRequired ) { OutDelegate outdelegate = new OutDelegate( OutText ); this.BeginInvoke(outdelegate, new object[]{text}); return; } txt.AppendText(text); txt.AppendText( "\t\n" ); }
注意,這裡的函式沒有返回,如果有返回,需要呼叫Invoke或者EndInvoke來獲得返回的結果,不要因為包裝而丟失了返回值。如果呼叫沒有完成,Invoke和EndInvoke都將會引起阻塞。
現在如果我有一個執行緒函式如下:
public void ThreadProc() { for(int i = 0; i < 5; i++) { OutText( i.ToString() ); Thread.Sleep(1000); } }
如果迴圈的次數很大,或者漏了Thread.Sleep(1000);,那麼你的UI肯定會停止響應,想知道原因嗎?看看BeginInvoke前面的物件,沒錯,就是this,也就是主執行緒,當你的主執行緒不停的呼叫OutText的時候,UI當然會停止響應。
與以前VC中建立一個新的執行緒需要呼叫AfxBeginThread函式,該函式中第一個引數就是執行緒函式的地址,而第二個引數是一個型別為LPVOID的指標型別,這個引數將傳遞給執行緒函式。現在我們沒有辦法再使用這種方法來傳遞引數了。我們需要將傳遞給執行緒的引數和執行緒函式包裝成一個單獨的類,然後在這個類的建構函式中初始化該執行緒所需的引數,然後再將該例項的執行緒函式傳遞給Thread類的建構函式。程式碼大致如下:
public class ProcClass { private string procParameter = ""; public ProcClass(string parameter) { procParameter = parameter; } public void ThreadProc() { } } ProcClass threadProc = new ProcClass("use thread class"); Thread thread = new Thread( new ThreadStart( threadProc.ThreadProc ) ); thread.IsBackground = true; thread.Start();
就是這樣,需要建立一箇中間類來傳遞執行緒所需的引數。
那麼如果我的執行緒又需要引數,又需要和UI進行互動的時候該怎麼辦呢?可以修改一下程式碼:
public class ProcClass { private string procParameter = ""; private Form1.OutDelegate delg = null; public ProcClass(string parameter, Form1.OutDelegate delg) { procParameter = parameter; this.delg = delg; } public void ThreadProc() { delg.BeginInvoke("use ProcClass.ThreadProc()", null, null); } } ProcClass threadProc = new ProcClass("use thread class", new OutDelegate(OutText)); Thread thread = new Thread( new ThreadStart( threadProc.ThreadProc ) ); thread.IsBackground = true; thread.Start();