WPF快速指導12: 執行緒處理模型

weixin_34219944發表於2011-02-07

WPF快速指導12: 執行緒處理模型

本文摘要:

1:理解與UI相關的多執行緒操作;
2:多個視窗多個執行緒
3:WPF中的多執行緒異常

1:理解與UI相關的多執行緒操作

    首先來說說傳統Winform。我們知道傳統Winform新起工作執行緒,在工作執行緒中不能對介面元素進行操作。如下面的程式碼,執行會報錯“執行緒間操作無效: 從不是建立控制元件“label1”的執行緒訪問它。”:

Thread t = new Thread(delegate()
{
label1.Text
= "temp";
});
t.Start();

     要使上面的程式碼能成功執行,我們需要使用控制元件的InvokeBeginInvoke和方法。這兩個方法的意思是說,讓賦值這個行為交給UI執行緒去處理。程式碼如下:

ContractedBlock.gifExpandedBlockStart.gif程式碼
Thread t = new Thread(delegate()
{
label1.Invoke(
new MethodInvoker(delegate()
{
label1.Text
= "temp";
}));
});
t.Start();

  而WPF的控制元件,我們找不到InvokeBeginInvoke這兩個方法了。因為WPF的UI執行緒都交給一個叫做排程器的類了。

     WPF 應用程式啟動時具有兩個執行緒:一個用於處理呈現,另一個用於管理 UI。 呈現執行緒實際上隱藏在後臺執行,而 UI 執行緒則接收輸入、處理事件、繪製螢幕以及執行應用程式程式碼。UI 執行緒在一個名為 Dispatcher 的物件中將工作項進行排隊。 Dispatcher 根據優先順序選擇工作項,並執行每一個工作項直到完成。Dispatcher 類提供兩種註冊工作項的方法:InvokeBeginInvoke。 這兩個方法都會安排執行一個委託。Invoke 是同步呼叫,即它直到 UI 執行緒實際執行完該委託時才返回。BeginInvoke 是非同步呼叫,因而將立即返回。

     上面的程式碼在WPF中的實現如下:

ContractedBlock.gifExpandedBlockStart.gif程式碼
Thread t = new Thread(new ThreadStart( delegate
{
tb_test.Dispatcher.Invoke(
new Action(delegate
{
tb_test.Text
= "123";
}),
null);
}));
t.Start();

     注意,WPF中已經沒有MethodInvoker這個類,我們使用Action代替。當然,你也可以使用自定義的委託宣告。


2:多個視窗多個執行緒

    一些 WPF 應用程式需要多個頂級視窗。 一個執行緒/Dispatcher 組合管理多個視窗完全可以接受,但有時使用多個執行緒更佳。 特別是在其中一個視窗有可能獨佔執行緒時,採用多個執行緒的優點更為突出。

    Windows 資源管理器即採用這種工作方式。 每個新的資源管理器視窗都屬於原始程式,但每個此類視窗都是在一個獨立執行緒的控制下建立的。該例子的簡單模仿如下程式碼:

 

ContractedBlock.gifExpandedBlockStart.gif程式碼
private void NewWindowHandler(object sender, RoutedEventArgs e)
{
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中的多執行緒異常

多執行緒的異常處理,要採用特殊的做法。以下的處理方式會存在問題: 

 

ContractedBlock.gifExpandedBlockStart.gif程式碼
try
{
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是屬於新起的異常,所以,正確的做法應該是: 

 

ContractedBlock.gifExpandedBlockStart.gif程式碼
Thread t = new Thread((ThreadStart)delegate
{
try
{
throw new Exception("多執行緒異常");
}
catch (Exception error)
{
MessageBox.Show(
"工作執行緒異常:" + error.Message + Environment.NewLine + error.StackTrace);
}
});
t.Start();

 

也就是說,新起的執行緒中異常的捕獲,可以將執行緒內部程式碼全部try起來。原則上來說,每個執行緒自己的異常應該在自己的內部處理完畢,不過仍舊有一個辦法,可以將執行緒內部的異常傳遞到主執行緒。

 

在WPF窗體程式中,你可以採用如下的方法將工作執行緒的異常傳遞到主執行緒:

 

ContractedBlock.gifExpandedBlockStart.gif程式碼
Thread t = new Thread((ThreadStart)delegate
{
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。

相關文章