• 星空雅梦


    WPF入门教程(四)--Dispatcher介绍

    一、Dispatcher介绍

    微软在WPF引入了Dispatcher,那么这个Dispatcher的主要作用是什么呢? 
    不管是WinForm应用程序还是WPF应用程序,实际上都是一个进程,一个进程可以包含多个线程,其中有一个是主线程,其余的是子线程。在WPF或WinForm应用程序中,主线程负责接收输入、处理事件、绘制屏幕等工作,为了使主线程及时响应,防止假死,在开发过程中对一些耗时的操作、消耗资源比较多的操作,都会去创建一个或多个子线程去完成操作,比如大数据量的循环操作、后台下载。这样一来,由于UI界面是主线程创建的,所以子线程不能直接更新由主线程维护的UI界面。

    二 走进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指的是键盘输入和鼠标移动、点击等等。

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

  • 相关阅读:
    20190211 模拟训练 A. 大猫咪
    如何诊断节点重启问题
    诊断 Grid Infrastructure 启动问题 (文档 ID 1623340.1)
    bzoj4025 二分图
    root.sh脚本支持checkpoints文件实现重复运行
    [IOI2018] seats 排座位
    最常见的 5 个导致节点重新启动、驱逐或 CRS 意外重启的问题 (文档 ID 1524455.1)
    [IOI2018] werewolf 狼人
    OCR/Vote disk 维护操作: (添加/删除/替换/移动) (文档 ID 1674859.1)
    [POI2011]ROT-Tree Rotations
  • 原文地址:https://www.cnblogs.com/LiZhongZhongY/p/10870070.html
Copyright © 2020-2023  润新知