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来封装这些线程级的操做。

相关文章
相关标签/搜索