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

1、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。框架

 

2、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 之類的形狀元素提供基類。

 

3、走進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 線程。

 

 

4、使用Dispatcher

下面咱們來用一個實例,來看看如何正確從一個非 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來封裝這些線程級的操做。

相關文章
相關標籤/搜索