轉自http://www.imwls.com/c-delegate-threading/,感覺這篇文章寫的很好,拿來分享一下,呵呵
最近寫到兩個程式都用到了多線程,但是由於需要調用主線程UI,所以總是會出錯,Google了一下,終於找到了使用Delegate(委託)的解決方案,現在貼出來存檔並且分享:
很多時候寫windows程式都需要結合多線程,在.net中用如下得代碼來建立並啟動一個新的線程。
public void ThreadProc(); Thread thread = new Thread(new ThreadStart(ThreadProc)); thread.IsBackground = true; thread.Start();
但是很多時候,在新的線程中,我們需要與UI進行互動,在.net中不允許我們直接這樣做。可以參考MSDN中的描述:
“Windows 表單”使用單一執行緒 Apartment (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( "tn" ); } OutDelegate outdelegate = new OutDelegate( OutText ); this.BeginInvoke(outdelegate, new object[]{text});
如果我們需要在另外一個線程裡面對UI進行操作,我們需要一個類似OutText的函數,還需要一個該函數的委託delegate,當然,這裡展示的是自訂的,.net中還有很多其他類型的委託,可以直接使用,不需要而外聲明。例如:MethodInvoker和EventHandler,這兩種類型委託的函數外觀是固定的,MethodInvoker是void Function()類型的委託,而EventHandler是void Function(object, EventArgs)類型的委託,第一個不支援參數,第二中的參數類型和數量都是固定的,這兩種委託可以很方便的調用,但是缺乏靈活性。請注意BeginInvoke前面的對象是this,也就是主線程。現在再介紹Control.InvokeRequired,Control是所有控制項的基類,對於這個屬性MSDN的描述是:
擷取一個值,該值指示調用方在對控制項進行方法調用時是否必須調用 Invoke 方法,因為調用方位於建立控制項所在的線程以外的線程中。
該屬性可用於確定是否必須調用 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( "tn" ); }
注意,這裡的函數沒有返回,如果有返回,需要調用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進行互動的時候該怎麼辦呢?可以修改一下代碼:
1234567891011121314151617181920212223242526272829303132333435363738394041 |
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(); //———————–以下為自己寫的非同步委託的例子———————— namespace PhsControlDelegate { public partial class Form2 : Form { PhsControl.phsControl phsObj = new PhsControl.phsControl(); public delegate void OperationDelegate(string strMessage); public Form2() { phsObj.OnJieShouDuanXin +=new PhsControl.JieShouDXHandler(phsObj_OnJieShouDuanXin); } public void SetText(string strMessage) { txtReceive.AppendText(strMessage); } protected void phsObj_OnJieShouDuanXin(Object sender, PhsControl.jsSmsArgs e) { OperationDelegate optionDelgate = new OperationDelegate(SetText); this.BeginInvoke(optionDelgate, new object[] { e.dhHaoMa + " " + e.dxNeiRong + "tn" }); } } } |