WPF入门教程系列四——Dispatcher介绍
WPF入門教程系列四——Dispatcher介紹
一、Dispatcher介紹
???? 微軟在WPF引入了Dispatcher,那么這個(gè)Dispatcher的主要作用是什么呢?
???? 不管是WinForm應(yīng)用程序還是WPF應(yīng)用程序,實(shí)際上都是一個(gè)進(jìn)程,一個(gè)進(jìn)程可以包含多個(gè)線程,其中有一個(gè)是主線程,其余的是子線程。在WPF或WinForm應(yīng)用程序中,主線程負(fù)責(zé)接收輸入、處理事件、繪制屏幕等工作,為了使主線程及時(shí)響應(yīng),防止假死,在開發(fā)過程中對一些耗時(shí)的操作、消耗資源比較多的操作,都會去創(chuàng)建一個(gè)或多個(gè)子線程去完成操作,比如大數(shù)據(jù)量的循環(huán)操作、后臺下載。這樣一來,由于UI界面是主線程創(chuàng)建的,所以子線程不能直接更新由主線程維護(hù)的UI界面。
????? Dispatcher的作用是用于管理線程工作項(xiàng)隊(duì)列,類似于Win32中的消息隊(duì)列,Dispatcher的內(nèi)部函數(shù),仍然調(diào)用了傳統(tǒng)的創(chuàng)建窗口類,創(chuàng)建窗口,建立消息泵等操作。Dispatcher本身是一個(gè)單例模式,構(gòu)造函數(shù)私有,暴露了一個(gè)靜態(tài)的CurrentDispatcher方法用于獲得當(dāng)前線程的Dispatcher。對于線程來說,它對Dispatcher是一無所知的,Dispatcher內(nèi)部維護(hù)了一個(gè)靜態(tài)的 List<Dispatcher> _dispatchers, 每當(dāng)使用CurrentDispatcher方法時(shí),它會在這個(gè)_dispatchers中遍歷,如果沒有找到,則創(chuàng)建一個(gè)新的Dispatcher對 象,加入到_dispatchers中去。Dispatcher內(nèi)部維護(hù)了一個(gè)Thread的屬性,創(chuàng)建Dispatcher時(shí)會把當(dāng)前線程賦值給這個(gè) Thread的屬性,下次遍歷查找的時(shí)候就使用這個(gè)字段來匹配是否在_dispatchers中已經(jīng)保存了當(dāng)前線程的Dispatcher。
?
二、Dispatcher的繼承關(guān)系
???? 在 WPF 的類層次結(jié)構(gòu)中,大部分都集中派生于 DispatcherObject 類(通過其他類)。如下圖?所示,您可以看到 DispatcherObject 虛擬類正好位于 Object 下方和大多數(shù) WPF 類的層次結(jié)構(gòu)之間。 要了解他們之間的關(guān)系可以參看下面這張類繼承關(guān)系圖:?
?
對上圖的一些說明:
1)? System.Object 類:大家都知道在.Net中所有類型的基類,DispatcherObject 就繼承于它,所以它是WPF的基類。
2)? System.Windows.Threading.DispatcherObject 類:從圖中看WPF 中的使用到的大部分控件與其他類大多是繼承 DispatcherObject 類,它提供了用于處理并發(fā)和線程的基本構(gòu)造。
3)? System.Windows.DependencyObject類:對WPF中的依賴項(xiàng)屬性承載支持與? 附加屬性承載支持,表示參與 依賴項(xiàng)屬性 系統(tǒng)的對象。
4)? System.Windows.Media.Visual類:為 WPF 中的呈現(xiàn)提供支持,其中包括命中測試、坐標(biāo)轉(zhuǎn)換和邊界框計(jì)算等。
5)? System.Windows.UIElement 類:UIElement 是 WPF 核心級實(shí)現(xiàn)的基類,該類是 Windows Presentation Foundation (WPF) 中具有可視外觀并可以處理基本輸入的大多數(shù)對象的基類。
6)? System.Windows.FrameworkElement類:為 Windows Presentation Foundation (WPF) 元素提供 WPF 框架級屬性集、事件集和方法集。此類表示附帶的 WPF 框架級實(shí)現(xiàn),它是基于由UIElement定義的 WPF 核心級 API 構(gòu)建的。
7)? System.Windows.Controls.Control 類:表示 用戶界面 (UI) 元素的基類,這些元素使用 ControlTemplate 來定義其外觀。
8)? System.Windows.Controls.ContentControl類:表示沒有任何類型的內(nèi)容表示單個(gè)控件。
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 類:表示可用于提供項(xiàng)目的集合的控件。?
?以條目集合位內(nèi)容的控件?ItemsControl
特點(diǎn): a.均派生自ItemsControl
??????? ? ?b.內(nèi)容屬性為Items或ItemsSource
????? ?? ???c.每種ItemsControl都對應(yīng)有自己的條目容器(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) 應(yīng)用程序的子對象。
11)System.Windows.Sharps.Sharp類:為 Ellipse、Polygon 和 Rectangle 之類的形狀元素提供基類。
?
三、走進(jìn)Dispatcher
????? 所有 WPF 應(yīng)用程序啟動時(shí)都會加載兩個(gè)重要的線程:一個(gè)用于呈現(xiàn)用戶界面,另一個(gè)用于管理用戶界面。呈現(xiàn)線程是一個(gè)在后臺運(yùn)行的隱藏線程,因此您通常面對的唯一線程 就是 UI 線程。WPF 要求將其大多數(shù)對象與 UI 線程進(jìn)行關(guān)聯(lián)。這稱之為線程關(guān)聯(lián),意味著要使用一個(gè) WPF 對象,只能在創(chuàng)建它的線程上使用。在其他線程上使用它會導(dǎo)致引發(fā)運(yùn)行時(shí)異常。 UI 線程的作用是用于接收輸入、處理事件、繪制屏幕以及運(yùn)行應(yīng)用程序代碼。
????? 在 WPF 中絕大部分控件都繼承自 DispatcherObject,甚至包括 Application。這些繼承自 DispatcherObject 的對象具有線程關(guān)聯(lián)特征,也就意味著只有創(chuàng)建這些對象實(shí)例,且包含了 Dispatcher 的線程(通常指默認(rèn) UI 線程)才能直接對其進(jìn)行更新操作。
????? DispatcherObject 類有兩個(gè)主要職責(zé):提供對對象所關(guān)聯(lián)的當(dāng)前 Dispatcher 的訪問權(quán)限,以及提供方法以檢查 (CheckAccess) 和驗(yàn)證 (VerifyAccess) 某個(gè)線程是否有權(quán)訪問對象(派生于 DispatcherObject)。CheckAccess 與 VerifyAccess 的區(qū)別在于 CheckAccess 返回一個(gè)布爾值,表示當(dāng)前線程是否可以使用對象,而 VerifyAccess 則在線程無權(quán)訪問對象的情況下引發(fā)異常。通過提供這些基本的功能,所有 WPF 對象都支持對是否可在特定線程(特別是 UI 線程)上使用它們加以確定。如下圖。?
??????? 在 WPF 中,DispatcherObject 只能通過與它關(guān)聯(lián)的 Dispatcher 進(jìn)行訪問。 例如,后臺線程不能更新由 UI 線程創(chuàng)建的 Label的內(nèi)容。
???????? 那么如何更新UI線程創(chuàng)建的對象信息呢?Dispatcher提供了兩個(gè)方法,Invoke和BeginInvoke,這兩個(gè)方法還有多個(gè)不同參數(shù)的重載。其中Invoke內(nèi)部還是調(diào)用了BeginInvoke,一個(gè)典型的BeginInvoke參數(shù)如下:
public DispatcherOperation BeginInvoke(Delegate method, DispatcherPriority priority, params object[] args);
?
?????? Invoke 是同步操作,而 BeginInvoke 是異步操作。 該這兩個(gè)操作將按指定的 DispatcherPriority 添加到 Dispatcher 的隊(duì)列中。 ????? DispatcherPriority定義了很多優(yōu)先級,可以分為前臺優(yōu)先級和后臺優(yōu)先級,其中前臺包括 Loaded~Send,后臺包括Background~Input。剩下的幾個(gè)優(yōu)先級除了Invalid和Inactive都屬于空閑優(yōu)先級。這個(gè)前臺優(yōu)先級和后臺優(yōu)先級的分界線是以Input來區(qū)分的,這里的Input指的是鍵盤輸入和鼠標(biāo)移動、點(diǎn)擊等等。
DispatchPriority 優(yōu)先級別
| 優(yōu)先級 | 說明 |
| Invalid | 這是一個(gè)無效的優(yōu)先級。 |
| Inactive | 工作項(xiàng)目已排隊(duì)但未處理。 |
| SystemIdle | 僅當(dāng)系統(tǒng)空閑時(shí)才將工作項(xiàng)目調(diào)度到 UI 線程。這是實(shí)際得到處理的項(xiàng)目的最低優(yōu)先級。 |
| ApplicationIdle | 僅當(dāng)應(yīng)用程序本身空閑時(shí)才將工作項(xiàng)目調(diào)度到 UI 線程。 |
| ContextIdle | 僅在優(yōu)先級更高的工作項(xiàng)目得到處理后才將工作項(xiàng)目調(diào)度到 UI 線程。 |
| Background | 在所有布局、呈現(xiàn)和輸入項(xiàng)目都得到處理后才將工作項(xiàng)目調(diào)度到 UI 線程。 |
| Input | 以與用戶輸入相同的優(yōu)先級將工作項(xiàng)目調(diào)度到 UI 線程。 |
| Loaded | 在所有布局和呈現(xiàn)都完成后才將工作項(xiàng)目調(diào)度到 UI 線程。 |
| Render | 以與呈現(xiàn)引擎相同的優(yōu)先級將工作項(xiàng)目調(diào)度到 UI 線程。 |
| DataBind | 以與數(shù)據(jù)綁定相同的優(yōu)先級將工作項(xiàng)目調(diào)度到 UI 線程。 |
| Normal | 以正常優(yōu)先級將工作項(xiàng)目調(diào)度到 UI 線程。這是調(diào)度大多數(shù)應(yīng)用程序工作項(xiàng)目時(shí)的優(yōu)先級。 |
| Send | 以最高優(yōu)先級將工作項(xiàng)目調(diào)度到 UI 線程。 |
?
?
四、使用Dispatcher
下面我們來用一個(gè)實(shí)例,來看看如何正確從一個(gè)非 UI 線程中更新一個(gè)由UI線程創(chuàng)建的對象。
1、錯(cuò)誤的更新方式
??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" >多線程同步調(diào)用</Button><Button Name="btnAppBeginInvoke" Click="btnAppBeginInvoke_Click" >BeginInvoke 異步調(diào)用</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(){// 模擬一些工作正在進(jìn)行Thread.Sleep(TimeSpan.FromSeconds(2));lblHello.Content = "歡迎你光臨WPF的世界,Dispatcher";}private void btnThd_Click(object sender, RoutedEventArgs e){Thread thread = new Thread(ModifyUI);thread.Start();}}}?
錯(cuò)誤截圖:
?
2、正確的更新方式,從上例中我們看到了從子線程中直接更新UI線程創(chuàng)建的對象,會報(bào)錯(cuò)。應(yīng)該如何修改呢?我們把上面的代碼修改成如下,再來看看會是什么效果。
???
private void ModifyUI(){// 模擬一些工作正在進(jìn)行Thread.Sleep(TimeSpan.FromSeconds(2));//lblHello.Content = "歡迎你光臨WPF的世界,Dispatcher";this.Dispatcher.Invoke(DispatcherPriority.Normal, (ThreadStart)delegate(){lblHello.Content = "歡迎你光臨WPF的世界,Dispatche 同步方法 !!";});}
當(dāng)然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();}五、小結(jié)
??? 在WPF中,所有的WPF對象都派生自DispatcherObject,DispatcherObject暴露了Dispatcher屬性用來取得創(chuàng)建 對象線程對應(yīng)的Dispatcher。DispatcherObject對象只能被創(chuàng)建它的線程所訪問,其他線程修改 DispatcherObject需要取得對應(yīng)的Dispatcher,調(diào)用Invoke或者BeginInvoke來投入任務(wù)。Dispatcher的一些設(shè)計(jì)思路包括 Invoke和BeginInvoke等從WinForm時(shí)代就是一直存在的,只是使用了Dispatcher來封裝這些線程級的操作。
轉(zhuǎn)載于:https://www.cnblogs.com/Jeely/p/11075946.html
總結(jié)
以上是生活随笔為你收集整理的WPF入门教程系列四——Dispatcher介绍的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot教程(7) – 直
- 下一篇: 转载 oracle12c 切换字符集