微軟在WPF引入了Dispatcher,那麼這個Dispatcher的主要做用是什麼呢?多線程
不論是WinForm應用程序仍是WPF應用程序,實際上都是一個進程,一個進程能夠包含多個線程,其中有一個是主線程,其他的是子線程。在WPF或WinForm應用程序中,主線程負責接收輸入、處理事件、繪製屏幕等工做,爲了使主線程及時響應,防止假死,在開發過程當中對一些耗時的操做、消耗資源比較多的操做,都會去建立一個或多個子線程去完成操做,好比大數據量的循環操做、後臺下載。這樣一來,因爲UI界面是主線程建立的,因此子線程不能直接更新由主線程維護的UI界面。併發
Dispatcher的做用是用於管理線程工做項隊列,相似於Win32中的消息隊列,Dispatcher的內部函數,仍然調用了傳統的建立窗口類,建立窗口,創建消息泵等操做。Dispatcher自己是一個單例模式,構造函數私有,暴露了一個靜態的CurrentDispatcher方法用於得到當前線程的Dispatcher。對於線程來講,它對Dispatcher是一無所知的,Dispatcher內部維護了一個靜態的 List<Dispatcher> _dispatchers, 每當使用CurrentDispatcher方法時,它會在這個_dispatchers中遍歷,若是沒有找到,則建立一個新的Dispatcher對 象,加入到_dispatchers中去。Dispatcher內部維護了一個Thread的屬性,建立Dispatcher時會把當前線程賦值給這個 Thread的屬性,下次遍歷查找的時候就使用這個字段來匹配是否在_dispatchers中已經保存了當前線程的Dispatcher。框架
在 WPF 的類層次結構中,大部分都集中派生於 DispatcherObject 類(經過其餘類)。如下圖 所示,您能夠看到 DispatcherObject 虛擬類正好位於 Object 下方和大多數 WPF 類的層次結構之間。 要了解他們之間的關係能夠參看下面這張類繼承關係圖: 異步
對上圖的一些說明:函數
1) System.Object 類:你們都知道在.Net中全部類型的基類,DispatcherObject 就繼承於它,因此它是WPF的基類。佈局
2) System.Windows.Threading.DispatcherObject 類:從圖中看WPF 中的使用到的大部分控件與其餘類大可能是繼承 DispatcherObject 類,它提供了用於處理併發和線程的基本構造。測試
3) System.Windows.DependencyObject類:對WPF中的依賴項屬性承載支持與 附加屬性承載支持,表示參與 依賴項屬性 系統的對象。大數據
4) System.Windows.Media.Visual類:爲 WPF 中的呈現提供支持,其中包括命中測試、座標轉換和邊界框計算等。this
5) System.Windows.UIElement 類:UIElement 是 WPF 核心級實現的基類,該類是 Windows Presentation Foundation (WPF) 中具備可視外觀並能夠處理基本輸入的大多數對象的基類。spa
6) System.Windows.FrameworkElement類:爲 Windows Presentation Foundation (WPF) 元素提供 WPF 框架級屬性集、事件集和方法集。此類表示附帶的 WPF 框架級實現,它是基於由UIElement定義的 WPF 核心級 API 構建的。
7) System.Windows.Controls.Control 類:表示 用戶界面 (UI) 元素的基類,這些元素使用 ControlTemplate 來定義其外觀。
8) System.Windows.Controls.ContentControl類:表示沒有任何類型的內容表示單個控件。
WPF的絕大部分的控件,還包括窗口自己都是繼承自ContentControl的。
ContentControl族包含的控件
Button |
ButtonBase |
CheckBox |
ComboBoxItem |
ContentControl |
Frame |
GridViewColumnHeader |
GroupItem |
Label |
ListBoxItem |
ListViewItem |
NavigationWindow |
RadioButton |
RepeatButton |
ScrollViewer |
StatusBarItem |
ToggleButton |
ToolTip |
UserControl |
Window |
9) System.Windows.Controls.ItemsControl 類:表示可用於提供項目的集合的控件。
以條目集合位內容的控件 ItemsControl
特色: a.均派生自ItemsControl
b.內容屬性爲Items或ItemsSource
c.每種ItemsControl都對應有本身的條目容器(Item Container).
ItemsControl族包含的控件
Menu |
MenuBase |
ContextMenu |
ComboBox |
ItemsControl |
ListBox |
ListView |
TabControl |
TreeView |
Selector |
StatusBar |
|
10) System.Windows.Controls.Panel類:爲全部 Panel 元素提供基類。 使用 Panel 元素定位和排列在 Windows Presentation Foundation (WPF) 應用程序的子對象。
11)System.Windows.Sharps.Sharp類:爲 Ellipse、Polygon 和 Rectangle 之類的形狀元素提供基類。
全部 WPF 應用程序啓動時都會加載兩個重要的線程:一個用於呈現用戶界面,另外一個用於管理用戶界面。呈現線程是一個在後臺運行的隱藏線程,所以您一般面對的惟一線程 就是 UI 線程。WPF 要求將其大多數對象與 UI 線程進行關聯。這稱之爲線程關聯,意味着要使用一個 WPF 對象,只能在建立它的線程上使用。在其餘線程上使用它會致使引起運行時異常。 UI 線程的做用是用於接收輸入、處理事件、繪製屏幕以及運行應用程序代碼。
在 WPF 中絕大部分控件都繼承自 DispatcherObject,甚至包括 Application。這些繼承自 DispatcherObject 的對象具備線程關聯特徵,也就意味着只有建立這些對象實例,且包含了 Dispatcher 的線程(一般指默認 UI 線程)才能直接對其進行更新操做。
DispatcherObject 類有兩個主要職責:提供對對象所關聯的當前 Dispatcher 的訪問權限,以及提供方法以檢查 (CheckAccess) 和驗證 (VerifyAccess) 某個線程是否有權訪問對象(派生於 DispatcherObject)。CheckAccess 與 VerifyAccess 的區別在於 CheckAccess 返回一個布爾值,表示當前線程是否可使用對象,而 VerifyAccess 則在線程無權訪問對象的狀況下引起異常。經過提供這些基本的功能,全部 WPF 對象都支持對是否可在特定線程(特別是 UI 線程)上使用它們加以肯定。以下圖。
在 WPF 中,DispatcherObject 只能經過與它關聯的 Dispatcher 進行訪問。 例如,後臺線程不能更新由 UI 線程建立的 Label的內容。
那麼如何更新UI線程建立的對象信息呢?Dispatcher提供了兩個方法,Invoke和BeginInvoke,這兩個方法還有多個不一樣參數的重載。其中Invoke內部仍是調用了BeginInvoke,一個典型的BeginInvoke參數以下:
public DispatcherOperation BeginInvoke(Delegate method, DispatcherPriority priority, params object[] args);
Invoke 是同步操做,而 BeginInvoke 是異步操做。 該這兩個操做將按指定的 DispatcherPriority 添加到 Dispatcher 的隊列中。 DispatcherPriority定義了不少優先級,能夠分爲前臺優先級和後臺優先級,其中前臺包括 Loaded~Send,後臺包括Background~Input。剩下的幾個優先級除了Invalid和Inactive都屬於空閒優先級。這個前臺優先級和後臺優先級的分界線是以Input來區分的,這裏的Input指的是鍵盤輸入和鼠標移動、點擊等等。
DispatchPriority 優先級別
優先級 |
說明 |
Invalid |
這是一個無效的優先級。 |
Inactive |
工做項目已排隊但未處理。 |
SystemIdle |
僅當系統空閒時纔將工做項目調度到 UI 線程。這是實際獲得處理的項目的最低優先級。 |
ApplicationIdle |
僅當應用程序自己空閒時纔將工做項目調度到 UI 線程。 |
ContextIdle |
僅在優先級更高的工做項目獲得處理後纔將工做項目調度到 UI 線程。 |
Background |
在全部佈局、呈現和輸入項目都獲得處理後纔將工做項目調度到 UI 線程。 |
Input |
以與用戶輸入相同的優先級將工做項目調度到 UI 線程。 |
Loaded |
在全部佈局和呈現都完成後纔將工做項目調度到 UI 線程。 |
Render |
以與呈現引擎相同的優先級將工做項目調度到 UI 線程。 |
DataBind |
以與數據綁定相同的優先級將工做項目調度到 UI 線程。 |
Normal |
以正常優先級將工做項目調度到 UI 線程。這是調度大多數應用程序工做項目時的優先級。 |
Send |
以最高優先級將工做項目調度到 UI 線程。 |
下面咱們來用一個實例,來看看如何正確從一個非 UI 線程中更新一個由UI線程建立的對象。
一、錯誤的更新方式
XAML代碼:
<Window x:Class="WpfApp1.WindowThd" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="WindowThd" Height="300" Width="400"> <Grid> <StackPanel> <Label x:Name="lblHello">歡迎你光臨WPF的世界!</Label> <Button Name="btnThd" Click="btnThd_Click" >多線程同步調用</Button> <Button Name="btnAppBeginInvoke" Click="btnAppBeginInvoke_Click" >BeginInvoke 異步調用</Button> </StackPanel> </Grid> </Window>
後臺代碼:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Shapes; namespace WpfApp1 { /// <summary> /// WindowThd.xaml 的交互邏輯 /// </summary> public partial class WindowThd : Window { public WindowThd() { InitializeComponent(); } private void ModifyUI() { // 模擬一些工做正在進行 Thread.Sleep(TimeSpan.FromSeconds(2)); lblHello.Content = "歡迎你光臨WPF的世界,Dispatcher"; } private void btnThd_Click(object sender, RoutedEventArgs e) { Thread thread = new Thread(ModifyUI); thread.Start(); } } }
錯誤截圖:
二、正確的更新方式,從上例中咱們看到了從子線程中直接更新UI線程建立的對象,會報錯。應該如何修改呢?咱們把上面的代碼修改爲以下,再來看看會是什麼效果。
private void ModifyUI() { // 模擬一些工做正在進行 Thread.Sleep(TimeSpan.FromSeconds(2)); //lblHello.Content = "歡迎你光臨WPF的世界,Dispatcher"; this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate() { lblHello.Content = "歡迎你光臨WPF的世界,Dispatche 同步方法 !!"; }); }
固然Dispatcher類也提供了BeginInvoke方法,咱們也可使用以下代碼,來完成對Lable的Content的更新。
private void btnAppBeginInvoke_Click(object sender, RoutedEventArgs e) { new Thread(() => { Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => { Thread.Sleep(TimeSpan.FromSeconds(2)); this.lblHello.Content = "歡迎你光臨WPF的世界,Dispatche 異步方法!!"+ DateTime.Now.ToString(); })); }).Start(); }
5、小結
在WPF中,全部的WPF對象都派生自DispatcherObject,DispatcherObject暴露了Dispatcher屬性用來取得建立 對象線程對應的Dispatcher。DispatcherObject對象只能被建立它的線程所訪問,其餘線程修改 DispatcherObject須要取得對應的Dispatcher,調用Invoke或者BeginInvoke來投入任務。Dispatcher的一些設計思路包括 Invoke和BeginInvoke等從WinForm時代就是一直存在的,只是使用了Dispatcher來封裝這些線程級的操做。