WPF入门教程系列四——Dispatcher介绍
WPF入門教程系列四——Dispatcher介紹
一、Dispatcher介紹
???? 微軟在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。
?
二、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 中的呈現提供支持,其中包括命中測試、坐標轉換和邊界框計算等。
5)? System.Windows.UIElement 類:UIElement 是 WPF 核心級實現的基類,該類是 Windows Presentation Foundation (WPF) 中具有可視外觀并可以處理基本輸入的大多數對象的基類。
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 之類的形狀元素提供基類。
?
三、走進Dispatcher
????? 所有 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 線程。 |
?
?
四、使用Dispatcher
下面我們來用一個實例,來看看如何正確從一個非 UI 線程中更新一個由UI線程創建的對象。
1、錯誤的更新方式
??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();}}}?
錯誤截圖:
?
2、正確的更新方式,從上例中我們看到了從子線程中直接更新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();}五、小結
??? 在WPF中,所有的WPF對象都派生自DispatcherObject,DispatcherObject暴露了Dispatcher屬性用來取得創建 對象線程對應的Dispatcher。DispatcherObject對象只能被創建它的線程所訪問,其他線程修改 DispatcherObject需要取得對應的Dispatcher,調用Invoke或者BeginInvoke來投入任務。Dispatcher的一些設計思路包括 Invoke和BeginInvoke等從WinForm時代就是一直存在的,只是使用了Dispatcher來封裝這些線程級的操作。
轉載于:https://www.cnblogs.com/Jeely/p/11075946.html
總結
以上是生活随笔為你收集整理的WPF入门教程系列四——Dispatcher介绍的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot教程(7) – 直
- 下一篇: 转载 oracle12c 切换字符集