1 UI線程執(zhí)行耗時操作 UI線程被阻塞 無法響應窗體消息隊列中的其他消息。
2 非UI線程修改UI屬性 由于窗體資源也屬于臨界資源 所以有互斥訪問的機制。
3 線程的同步問題 線程A等待線程B執(zhí)行完畢后才能開始執(zhí)行。
問題1的解決方法: 解決方法只有一種,就是開啟新線程執(zhí)行耗時操作,使原界面線程仍能夠響應窗體消息隊列中的用戶消息及系統(tǒng)消息。
開啟新線程的方式有以下各種: 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) 為需要異步執(zhí)行的耗時方法定義一個委托,使用該委托的實例的BeginInvoke方法來異步調用該方法,BeginInvoke方法附帶了AsyncCallback類型的回調函數委托以及object類型的參數。 然后可以在AsyncCallback類型的回調函數中使用EndInvoke方法來得到異步方法的返回值。
3) 可以使用System.Timers.Timer定時器類來實現在新線程中執(zhí)行耗時操作,System.Timers.Timer定時器不同于System.Windows.Forms.Timer定時器,System.Timers.Timer定時器的定時事件的響應函數并不是在調用定時器Start方法的線程中去執(zhí)行。
4) 可以使用BackgroundWorker組件來實現在新線程中執(zhí)行耗時操作(通過訂閱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類的靜態(tài)成員CheckForIllegalCrossThreadCalls的值(該成員用來表示是否啟用安全模式,安全模式的意思就是禁止跨線程修改界面屬性來避免多線程訪問臨界資源死鎖的問題),第二個判斷的屬性是InvokeRequired屬性(該屬性用來表示當前方法是否是在跨線程調用)。 所以我們可以通過修改CheckForIllegalCrossThreadCalls屬性為False來關閉安全模式,但有可能造成線程死鎖問題。
解決方法只有兩個 1) 設置CheckForIllegalCrossThreadCalls屬性為False,關閉.net的安全模式,在對界面屬性修改的代碼加上lock,來實現同一時間僅有一個線程修改界面屬性。 2) 在設置界面屬性的方法中詢問InvokeRequired屬性,如果是非界面線程修改界面屬性,則讓界面線程來調用設置界面屬性的方法。(這個方法是MSDN實例中慣用的方法,也是BackgroundWorker等組件的內部實現方式)
推薦大家使用BackgroundWorker來實現耗時操作的輔助線程以及跨線程修改界面屬性等操作,關于BackgroundWorker的具體使用方法見 一日一練 之 BackgroundWorker
|