1. 程式人生 > >WPF入門教程系列四——Dispatcher介紹

WPF入門教程系列四——Dispatcher介紹

匹配 添加 itl 屬性 one tree 獲得 都對 toggle

一、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來封裝這些線程級的操作。

WPF入門教程系列四——Dispatcher介紹