多線程Winform編程會帶來的常見問題
1 UI線程執行耗時操作 UI線程被阻塞 無法響應表單訊息佇列中的其他訊息。
2 非UI線程修改UI屬性 由於表單資源也屬於臨界資源 所以有互斥訪問的機制。
3 線程的同步問題 線程A等待線程B執行完畢後才能開始執行。
問題1的解決方案:
解決方案只有一種,就是開啟新線程執行耗時操作,使原介面線程仍能夠響應表單訊息佇列中的使用者訊息及系統訊息。
開啟新線程的方式有以下各種:
1) 使用System.Threading.Thread類與System.Threading.ThreadStart委託或System.Threading.ParameterizedThreadStart委託來實現開啟新線程。
ThreadStart委託的類型: void ThreadStart(void);
ParameterizedThreadStart委託的類型: void ParameterizedThreadStart(object[]);
ThreadStart委託可以指向一個無參數無傳回值的方法。
ParameterizedThreadStart委託可以指向一個有參數無傳回值的方法。
Thread類執行個體化的時候可以向建構函式傳入ThreadStart委託的執行個體或ParameterizedThreadStart委託的執行個體,然後使用Thread.Start()以非同步方式調用一個方法。
2) 為需要非同步執行的耗時方法定義一個委託,使用該委託的執行個體的BeginInvoke方法來非同步呼叫該方法,BeginInvoke方法附帶了AsyncCallback類型的回呼函數委託以及object類型的參數。
然後可以在AsyncCallback類型的回呼函數中使用EndInvoke方法來得到非同步方法呼叫的傳回值。
3) 使用ThreadPool.QueueUserWorkItem(new WaitCallback(method))來向線程池添加一個新線程
4) 可以使用System.Timers.Timer定時器類來實現在新線程中執行耗時操作,System.Timers.Timer定時器不同於System.Windows.Forms.Timer定時器,System.Timers.Timer定時器的定時事件的響應函數並不是在調用定時器Start方法的線程中去執行。
5) 可以使用BackgroundWorker組件來實現在新線程中執行耗時操作(通過訂閱DoWork事件).
問題2的解決方案
以下代碼是.NetFramework 2.0類庫中避免多線程修改介面造成的臨界資源死結問題的代碼。
System.Windows.Forms.Control.get_Handle方法的內部實現
public IntPtr get_Handle()
{
if ((checkForIllegalCrossThreadCalls && !inCrossThreadSafeCall) && this.InvokeRequired)
{
throw new InvalidOperationException(SR.GetString("IllegalCrossThreadCall", new object[] { this.Name }));
}
if (!this.IsHandleCreated)
{
this.CreateHandle();
}
return this.HandleInternal;
}
在修改每個控制項的屬性的時候,都會先調用get_Handle方法擷取一個操作控制代碼,在該方法內部會判斷Control類的靜態成員CheckForIllegalCrossThreadCalls的值(該成員用來表示是否啟用安全模式,安全模式的意思就是禁止跨線程修改介面屬性來避免多線程訪問臨界資源死結的問題),第二個判斷的屬性是InvokeRequired屬性(該屬性用來表示當前方法是否是在跨線程調用)。 所以我們可以通過修改CheckForIllegalCrossThreadCalls屬性為False來關閉安全模式,但有可能造成線程死結問題。
解決方案只有兩個
1) 設定CheckForIllegalCrossThreadCalls屬性為False,關閉.net的安全模式,在對介面屬性修改的代碼加上lock,來實現同一時間僅有一個線程修改介面屬性。
2) 在設定介面屬性的方法中詢問InvokeRequired屬性,如果是非介面線程修改介面屬性,則讓介面線程來調用設定介面屬性的方法。(這個方法是MSDN執行個體中慣用的方法,也是BackgroundWorker等組件的內部實現方式)
推薦大家使用BackgroundWorker來實現耗時操作的輔助線程以及跨線程修改介面屬性等操作,關於BackgroundWorker的具體使用方法見 一日一練 之 BackgroundWorker
http://www.cnblogs.com/coderlee/archive/2007/12/27/1017620.html