WPF快速指導12: 執行緒處理模型
本文摘要:
1:理解與UI相關的多執行緒操作;
2:多個視窗多個執行緒
3:WPF中的多執行緒異常
1:理解與UI相關的多執行緒操作
首先來說說傳統Winform。我們知道傳統Winform新起工作執行緒,在工作執行緒中不能對介面元素進行操作。如下面的程式碼,執行會報錯“執行緒間操作無效: 從不是建立控制元件“label1”的執行緒訪問它。”:
{
label1.Text = "temp";
});
t.Start();
要使上面的程式碼能成功執行,我們需要使用控制元件的Invoke 和 BeginInvoke和方法。這兩個方法的意思是說,讓賦值這個行為交給UI執行緒去處理。程式碼如下:
{
label1.Invoke(new MethodInvoker(delegate()
{
label1.Text = "temp";
}));
});
t.Start();
而WPF的控制元件,我們找不到Invoke 和 BeginInvoke這兩個方法了。因為WPF的UI執行緒都交給一個叫做排程器的類了。
WPF 應用程式啟動時具有兩個執行緒:一個用於處理呈現,另一個用於管理 UI。 呈現執行緒實際上隱藏在後臺執行,而 UI 執行緒則接收輸入、處理事件、繪製螢幕以及執行應用程式程式碼。UI 執行緒在一個名為 Dispatcher 的物件中將工作項進行排隊。 Dispatcher 根據優先順序選擇工作項,並執行每一個工作項直到完成。Dispatcher 類提供兩種註冊工作項的方法:Invoke 和 BeginInvoke。 這兩個方法都會安排執行一個委託。Invoke 是同步呼叫,即它直到 UI 執行緒實際執行完該委託時才返回。BeginInvoke 是非同步呼叫,因而將立即返回。
上面的程式碼在WPF中的實現如下:
{
tb_test.Dispatcher.Invoke(new Action(delegate
{
tb_test.Text = "123";
}), null);
}));
t.Start();
注意,WPF中已經沒有MethodInvoker這個類,我們使用Action代替。當然,你也可以使用自定義的委託宣告。
2:多個視窗多個執行緒
一些 WPF 應用程式需要多個頂級視窗。 一個執行緒/Dispatcher 組合管理多個視窗完全可以接受,但有時使用多個執行緒更佳。 特別是在其中一個視窗有可能獨佔執行緒時,採用多個執行緒的優點更為突出。
Windows 資源管理器即採用這種工作方式。 每個新的資源管理器視窗都屬於原始程式,但每個此類視窗都是在一個獨立執行緒的控制下建立的。該例子的簡單模仿如下程式碼:
{
Thread newWindowThread = new Thread(new ThreadStart(ThreadStartingPoint));
newWindowThread.SetApartmentState(ApartmentState.STA);
newWindowThread.IsBackground = true;
newWindowThread.Start();
}
private void ThreadStartingPoint()
{
Window1 tempWindow = new Window1();
tempWindow.Show();
System.Windows.Threading.Dispatcher.Run();
}
3:WPF中的多執行緒異常
多執行緒的異常處理,要採用特殊的做法。以下的處理方式會存在問題:
{
Thread t = new Thread((ThreadStart)delegate
{
throw new Exception("多執行緒異常");
});
t.Start();
}
catch (Exception error)
{
MessageBox.Show(error.Message + Environment.NewLine + error.StackTrace);
}
應用程式並不會在這裡捕獲執行緒t中的異常,而是會直接退出。從.NET2.0開始,任何執行緒上未處理的異常,都會導致應用程式的退出(先會觸發AppDomain的UnhandledException)。上面程式碼中的try-catch實際上捕獲的還是當前執行緒的異常,而t是屬於新起的異常,所以,正確的做法應該是:
{
try
{
throw new Exception("多執行緒異常");
}
catch (Exception error)
{
MessageBox.Show("工作執行緒異常:" + error.Message + Environment.NewLine + error.StackTrace);
}
});
t.Start();
也就是說,新起的執行緒中異常的捕獲,可以將執行緒內部程式碼全部try起來。原則上來說,每個執行緒自己的異常應該在自己的內部處理完畢,不過仍舊有一個辦法,可以將執行緒內部的異常傳遞到主執行緒。
在WPF窗體程式中,你可以採用如下的方法將工作執行緒的異常傳遞到主執行緒:
{
try
{
throw new Exception("非窗體執行緒異常");
}
catch (Exception ex)
{
this.Dispatcher.Invoke((Action)delegate
{
throw ex;
});
}
});
t.Start();
WPF窗體程式的處理方式與Windows窗體程式比較,有兩個很有意思的地方:
第一個是,在Windows窗體中,我們採用的是BeginInvoke方法。你會發現使用Invoke方法,並不能引發主執行緒的Application.ThreadException。而在WPF窗體程式中,無論是排程器的Invoke還是BeginInvoke方法都能將異常傳遞給主執行緒。
第二個地方就是InnerException。WPF的工作執行緒異常將會拋到主執行緒,變成主執行緒異常的InnerException,而Windows窗體程式的工作執行緒異常,將會被吃掉,直接變為null,只是在異常的Message資訊中儲存工作執行緒異常的Message。