建議87:區(qū)分WPF和WinForm的線程模型
WPF和WinForm窗體應(yīng)用程序都有一個要求,那就是UI元素(如Button、TextBox等)必須由創(chuàng)建它的那個線程進行更新。WinForm在這方面的限制并不是很嚴(yán)格,所以像下面這樣的代碼,在WinForm中大部分情況下還能運行(本建議后面會詳細(xì)解釋為什么會出現(xiàn)這種現(xiàn)象):
private void buttonStartAsync_Click(
object sender, EventArgs e)
{ Task t =
new Task(() =>
{ while (
true) { label1.Text =
DateTime.Now.ToString(); Thread.Sleep(1000); } }); //如果有異常,就啟動一個新任務(wù) t.ContinueWith((task) =>
{ try { task.Wait(); } catch (AggregateException ex) { foreach (Exception inner
in ex.InnerExceptions) { MessageBox.Show(string.Format(
"異常類型:{0}{1}來自:{2}{3}異常內(nèi)容:{4}", inner.GetType(),Environment.NewLine,
inner.Source, Environment.NewLine, inner.Message)); } } }, TaskContinuationOptions.OnlyOnFaulted); t.Start();
} 但是,相同的一段代碼如果放到WPF環(huán)境中,就肯定會拋出System.InvalidOperationException異常。
理論上,WinForm和WPF的線程模型非常接近,它們最后都是調(diào)用API(GetMessage或PeekMessage)來處理其他線程發(fā)送過來的消息,這些消息存儲在系統(tǒng)的一個消息隊列中。在WinForm和WPF中,創(chuàng)建主界面的線程就是主線程,也就是UI線程,UI線程負(fù)責(zé)處理該消息隊列。只是兩者在處理消息隊列的上層機制上稍微有一些不同,這就造成了同樣的代碼得到不同的結(jié)果。
在WinForm框架中有一個ISynchronizeInvoke接口,所有的UI元素(表現(xiàn)為Control)都繼承了該接口。其中,接口中的InvokdRequired屬性表示了當(dāng)前線程是否是創(chuàng)建它的線程。接口中的Invoke和BeginInvoke方法負(fù)責(zé)將消息發(fā)送到消息隊列中,這樣,UI線程就能夠正確處理它了。那么,上面的這段代碼在WinForm上的改進版本為(僅列出While循環(huán)部分):
while (
true)
{ if (label1.InvokeRequired) label1.BeginInvoke(new Action(() =>
{ label1.Text =
DateTime.Now.ToString(); })); else label1.Text =
DateTime.Now.ToString(); Thread.Sleep(1000);
} BeginInvoke方法接受的是一個Delegate類型的參數(shù),在這里我們用一個Action來實現(xiàn)。
WPF應(yīng)用程序的線程模型則完全依賴于DispatcherObject類型。所有的WPF控件都繼承自一個抽象類Visual,而這個抽象類又最終繼承自DispatcherObject類型。在這個DispatcherObject類型中有一個屬性,兩個方法。屬性Dispatcher完成所有的工作線程和UI線程之間的調(diào)度任務(wù)。CheckAccess方法負(fù)責(zé)檢測工作線程是否可以訪問控件,如果是,則返回True;否則返回False。VerifyAccess方法則負(fù)責(zé)檢測工作線程是否具有控件的訪問權(quán)限,如果不能訪問則拋出異常InvalidOperationException。
WinForm應(yīng)用程序用類似CheckAccess的方式進行訪問權(quán)限的判斷;WPF應(yīng)用程序則進行了改進,所有的UI控件都采用VerifyAccess的方式進行工作線程訪問權(quán)限的判斷。這直接決定了本建議開頭處那個例子的輸出,WPF只要判斷出工作線程和UI線程不是同一個線程的,則直接拋出異常,而WinForm卻有成功執(zhí)行的余地。但是,WinForm的這種機制直接造成了程序的不穩(wěn)定,因為即使在大部分情況下代碼能很好的工作,可是在不確定的情況下,那樣的代碼中工作線程會直接操作UI元素,這樣還是會拋出異常的。
考慮到WinForm在這個問題上的局限性,再次對WinForm的線程模型處理進行改進:
//用于表示主線程,在本例中就是UI線程
Thread mainThread; bool CheckAccess()
{ return mainThread ==
Thread.CurrentThread;
} void VerifyAccess()
{ if (!
CheckAccess()) throw new InvalidOperationException(
"調(diào)用線程無法訪問此對象,因為另一個線程擁有此對象");
} private void buttonStartAsync_Click(
object sender, EventArgs e)
{ //當(dāng)前線程就是主線程 mainThread =
Thread.CurrentThread; Task t =
new Task(() =>
{ while (
true) { if (!
CheckAccess()) label1.BeginInvoke(new Action(() =>
{ label1.Text =
DateTime.Now.ToString(); })); else label1.Text =
DateTime.Now.ToString(); Thread.Sleep(1000); } }); //如果有異常,就啟動一個新任務(wù) t.ContinueWith((task) =>
{ try { task.Wait(); } catch (AggregateException ex) { foreach (Exception inner
in ex.InnerExceptions) { MessageBox.Show(string.Format(
"異常類型:{0}{1}來自:{2}{3}異常內(nèi)容:{4}", inner.GetType(), Environment.NewLine, inner.Source, Environment.NewLine, inner.Message)); } } }, TaskContinuationOptions.OnlyOnFaulted); t.Start();
} ?
在這段代碼中,我們模擬WPF中DispatcherObject的兩個方法CheckAccess和VerifyAccess對線程模型進行了重新處理,增強了系統(tǒng)的穩(wěn)定性。在實際工作中,我們也可以提取這兩個方法為擴展方法,以便項目中的所有UI類型都能使用到。
WPF支持這兩個方法,其全部代碼如下所示(注意查看While循環(huán)部分):
private void buttonStart_Click(
object sender, RoutedEventArgs e)
{ Task t =
new Task(() =>
{ while (
true) { this.Dispatcher.BeginInvoke(
new Action(() =>
{ textBlock1.Text =
DateTime.Now.ToString(); })); Thread.Sleep(1000); } }); //為了捕獲異常,啟動了一個新任務(wù) t.ContinueWith((task) =>
{ try { task.Wait(); } catch (AggregateException ex) { foreach (Exception inner
in ex.InnerExceptions) { MessageBox.Show(string.Format(
"異常類型:{0}{1}來自:{2}{3}異常內(nèi)容:{4}", inner.GetType(), Environment.NewLine, inner.Source, Environment.NewLine, inner.Message)); } } }, TaskContinuationOptions.OnlyOnFaulted); t.Start();
} ?
注意 為了演示方便,本建議中的異常沒有傳遞到主線程。在實際編碼中,應(yīng)當(dāng)始終考慮將異常包裝到主線程。
?
?
轉(zhuǎn)自:《編寫高質(zhì)量代碼改善C#程序的157個建議》陸敏技
轉(zhuǎn)載于:https://www.cnblogs.com/jesselzj/p/4743508.html
總結(jié)
以上是生活随笔為你收集整理的编写高质量代码改善C#程序的157个建议——建议87:区分WPF和WinForm的线程模型...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。