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

时间: 2024-11-05 20:59:02

WPF入门教程系列四——Dispatcher介绍的相关文章

WPF入门教程系列二——Application介绍

原文:WPF入门教程系列二--Application介绍 一.Application介绍 WPF和WinForm 很相似, WPF与WinForm一样有一个 Application对象来进行一些全局的行为和操作,并且每个 Domain (应用程序域)中仅且只有一个 Application 实例存在.和 WinForm 不同的是WPF Application默认由两部分组成 : App.xaml 和 App.xaml.cs,这有点类似于 Asp.Net WebForm,将定义和行为代码相分离. 微

WPF入门教程系列六——布局介绍与Canvas

从这篇文章开始是对WPF中的界面如何布局做一个较简单的介绍,大家都知道:UI是做好一个软件很重要的因素,如果没有一个漂亮的UI,功能做的再好也无法吸引很多用户使用,而且没有漂亮的界面,那么普通用户会感觉这个软件没有多少使用价值. 一. 总体介绍 WPF的布局控件都在System.Windows.Controls.Panel这个基类下面,使用 WPF提供的各种控件在WPF应用程序中界面进行布局,同时对各种子控件(如按钮.文本框,下拉框等)进行排列组合. Pane类的公共属性太多了.就简单介绍几个常

WPF入门教程系列六——布局介绍与Canvas(一)

从这篇文章开始是对WPF中的界面如何布局做一个较简单的介绍,大家都知道:UI是做好一个软件很重要的因素,如果没有一个漂亮的UI,功能做的再好也无法吸引很多用户使用,而且没有漂亮的界面,那么普通用户会感觉这个软件没有多少使用价值. 一. 总体介绍 WPF的布局控件都在System.Windows.Controls.Panel这个基类下面,使用 WPF提供的各种控件在WPF应用程序中界面进行布局,同时对各种子控件(如按钮.文本框,下拉框等)进行排列组合. Pane类的公共属性太多了.就简单介绍几个常

WPF入门教程系列五——Window 介绍

一.窗体类基本概念 对于WPF应用程序,在Visual Studio和Expression Blend中,自定义的窗体均继承System.Windows.Window类.用户通过窗口与 Windows Presentation Foundation (WPF) 独立应用程序进行交互. 窗口的主要用途是承载可视化数据并使用户可以与数据进行交互的内容.独立 WPF 应用程序使用 Window 类来提供它们自己的窗口.在 WPF 中,可以使用代码或 XAML 标记来实现窗口的外观和行为.我们这里定义的

WPF入门教程系列三——Application介绍(续)

http://home.juedui100.com/user/23979225.htmlhttp://home.juedui100.com/user/23784255.htmlhttp://home.juedui100.com/detail/23844986.htmlhttp://home.juedui100.com/user/23844986.htmlhttp://home.juedui100.com/user/23933152.htmlhttp://home.juedui100.com/us

WPF入门教程系列(一) 创建你的第一个WPF项目

WPF入门教程系列(一) 创建你的第一个WPF项目 WPF基础知识 快速学习绝不是从零学起的,良好的基础是快速入手的关键,下面先为大家摞列以下自己总结的学习WPF的几点基础知识: 1) C#基础语法知识(或者其他.NET支持的语言):这个是当然的了,虽然WPF是XAML配置的,但是总还是要写代码的,相信各位读者应该也都有这个基础了. 2) HTML语言:虽然WPF是窗体程序但是由于使用的XAML语言,如果以前接触过HTML.XHTML.ASP.NET之路的东西的话会,接受这些标签会很有帮助的,如

[转载]WPF入门教程系列一——基础

一. 前言   最近在学习WPF,学习WPF首先上的是微软的MSDN,然后再搜索了一下网络有关WPF的学习资料.为了温故而知新把学习过程记录下来,以备后查.这篇主要讲WPF的开发基础,介绍了如何使用Visual Studio 2013创建一个WPF应用程序. 首先说一下学习WPF的基础知识: 1) 要会一门.NET所支持的编程语言.例如C#. 2) 会一点“标准通用标记语言”:WPF窗体程序使用的XAML语言,也属于“标准通用标记语言”的一个分支.如果以前接触过XML.HTML.XHTML.AS

WPF入门教程系列(二) 深入剖析WPF Binding的使用方法

WPF入门教程系列(二) 深入剖析WPF Binding的使用方法 同一个对象(特指System.Windows.DependencyObject的子类)的同一种属性(特指DependencyProperty)只能拥有一个binding. 这一点可以通过设置binding对象的方法名得知: public static BindingExpressionBase SetBinding( DependencyObject target, DependencyProperty dp, BindingB

WPF入门教程系列十四——依赖属性(四)

六.依赖属性回调.验证及强制值 我们通过下面的这幅图,简单介绍一下WPF属性系统对依赖属性操作的基本步骤: 借用一个常见的图例,介绍一下WPF属性系统对依赖属性操作的基本步骤: 第一步,确定Base Value,对同一个属性的赋值可能发生在很多地方.比如控件的背景(Background),可能在Style或者控件的构造函数中都对它进行了赋值,这个Base Value就要确定这些值中优先级最高的值,把它作为Base Value. 第二步,估值.如果依赖属性值是计算表达式(Expression),比