博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
DispatcherHelper
阅读量:6370 次
发布时间:2019-06-23

本文共 6926 字,大约阅读时间需要 23 分钟。

DispatcherHelper

通常,WPF 应用程序从两个线程开始:一个用于处理呈现,

一个用于管理 UI。呈现线程有效地隐藏在后台运行,而 UI 线程则接收输入、处理事件、绘制屏幕
以及运行应用程序代码。所以我们的大多数操作都会在UI线程中执行,同时它也处理绘制屏幕,如果我们
的一个操作相当耗时,那么它就没有机会处理绘制屏幕,此时我们是不能够拖动窗口的,也就是通常
说的屏幕卡住了。

Dispatcher,DispatcherObject,DependencyObject

Dispatcher即调度器,我们可以把UI线程看作CPU,我们的每个操作就是指令,指令发送到CPU处理,

CPU同时只能处理一个指令,当指令非常多时,优先级高的指令要先处理,低的可以稍后处理,Dispatcher
就是用于处理这些操作的,它将我们的操作根据优先级排队,等待UI线程来处理。

在WPF中,所有的WPF对象都派生自DispatcherObject,

DispatcherObject暴露了Dispatcher属性用来取得创建对象线程对应的Dispatcher。
鉴于线程亲缘性,DispatcherObject对象只能被创建它的线程所访问,
其他线程修改DispatcherObject需要取得对应的Dispatcher,
调用Invoke或者BeginInvoke来投入任务。
一个UI线程至少有一个Dispatcher来建立消息泵处理任务,一个Dispatcher只能对应一个UI线程。

WPF中的控件都是主UI线程创建的,也就是只有主UI线程可以访问他们,如果是其他线程访问控件

那么就会报错

Task.Factory.StartNew(() =>        {            btn.Content = "HelloWorld";        });

跨线程错误

解决办法就是在主线程中访问他们,或是

Task.Factory.StartNew(() =>        {            //耗时操作            Thread.Sleep(5000);            //最后结果通过主线程更新到控件            btn.Dispatcher.Invoke(() =>            {                btn.Content = "HelloWorld";            });                    });

在WPF中,Dispatcher,DispatcherObject和DependencyObject决定了一个对象的线程亲缘性,这里提供一个方便的网址

我们首先查看Dispatcher的源码,发现两个比较重要的方法和一个静态属性

Dispatcher

|____CurrentDispatcher
|____CheckAccess()
|____VerifyAccess()

CurrentDispatcher获取当前线程的Dispatcher,如果当前线程没有Dispatcher,那么就创建一个

CheckAccess()方法用于判断,当前Dispatcher所属的线程是不是当前线程
VerifyAccess()其实就是调用的CheckAccess,如果Dispatcher不属于当前线程,那么就报异常,这就是上面图片所示的异常

我们在来看DispatcherObject,看到一个构造函数,2个方法和一个属性

DispatcherObject

|____DispatcherObject
|____Dispatcher
|____CheckAccess()
|____VerifyAccess()

当创建一个DispatcherObject对象的时候,会给这个对象分配一个当前线程的Dispatcher,其他两个方法也就是对Dispatcher方法的封装

好,最后我们来看DependencyObject,看到两个重要的方法GetValue,SetValue

DependencyObject

|____SetValue(DependencyProperty dp,object value)
|____GetValue(DependencyProperty dp)

这两个方法中的的第一句就是this.VerifyAccess(),而DependencyObject是直接继承自DispatcherObject的,其实就是使用的Dispatcher的VerifyAccess,到此

WPF对象的线程亲缘性已经很明了了,所以,一个对象只能被它所创建的线程所访问。

Binding的源在多线程中怎么处理

网上比较好的文章

我的理解

我说下我的理解,我们在ViewModel中更新数据都要触发OnPropertyChanged事件,这个事件其实是被

一个叫做PropertyChangedEventManager的家伙订阅的,它又会去触发相应控件的OnPropertyChanged事件
,都是事件,事件也是方法,在哪个线程促发的事件,就在哪个线程处理,照这么说,我们在非UI线程
更新Model的属性,那么对应的会在非UI线程处理与之相绑定的控件,这里就会报如上的错误。我们做个
实验来测试下是不是这样的。

实验

界面上显示一个Teacher的基本数据,Name,Age,还有所管理的学生Student,我们在非UI线程做

以下操作

  • 修改Teacher的属性
  • 增加学生数
  • 修改最后一名学生的姓名

Teacher.cs

public class Teacher : ObservableObject{    private string _name;    private int _age;    private ObservableCollection
_students; public string Name { get { return _name; } set { _name = value; RaisePropertyChanged(() => Name); } } public int Age { get { return _age; } set { _age = value; RaisePropertyChanged(() => Age); } } public ObservableCollection
Students { get { return _students; } set { _students = value; RaisePropertyChanged(() => Students); } }}

Student.cs

public class Student : ObservableObject{    private string _name;    private int _age;    public string Name    {        get        {            return _name;        }        set        {            _name = value;            RaisePropertyChanged(() => Name);        }    }    public int Age    {        get        {            return _age;        }        set        {            _age = value;            RaisePropertyChanged(() => Name);        }    }}

MainView.xaml

MainViewModel.cs

public class MainViewModel : ViewModelBase{    private Teacher _teacher;    public Teacher Teacher    {        get        {            return _teacher;        }        set        {            _teacher = value;            RaisePropertyChanged(() => Teacher);        }    }    public RelayCommand ChangeTeacherNameCommand    {        get; set;    }    public RelayCommand AddStudentCommand    {        get; set;    }    public RelayCommand ChangeLastStudentNameCommand    {        get; set;    }    ///     /// Initializes a new instance of the MainViewModel class.    ///     public MainViewModel()    {        Teacher = new Teacher()        {            Name = "LaoZhao",            Age = 30,            Students = new ObservableCollection
() { new Student() { Name="LaoZhange", Age = 18 } } }; InitCommand(); } private void InitCommand() { ChangeTeacherNameCommand = new RelayCommand(() => { Task.Factory.StartNew(() => { Teacher.Name = "MaYun"; }); }); AddStudentCommand = new RelayCommand(() => { Task.Factory.StartNew(() => { Teacher.Students.Add(new Student() { Name = "LaoLi", Age = 25 }); }); }); ChangeLastStudentNameCommand = new RelayCommand(() => { Task.Factory.StartNew(() => { var student = Teacher.Students.LastOrDefault(); if (student != null) { student.Name = "TheLast"; } }); }); }}

最后发现,在非UI线程更新Teacher的姓名和Student的姓名是没有问题的,那是因为WPF在后台强制

使用了UI线程,然而向集合中增加一个学生却报错~(我猜测WPF针对有些控件不会帮我们切换到UI主线程),为了避免这些情况,如果要更新Model的属性,
请在UI线程中。但是我们都是在ViewModel中,并不知道任何一个控件,自然也没有办法拿到Dispatcher
好了,主角终于登场了DispatcherHelper

DispatcherHelper

DispatcherHelper的使用首先要初始化用来保存主线程的Dispatcher,这个Dispatcher即创建界面的

线程,哪个线程创建的控件,只能由那个线程才能访问。所以我们一般在App的构造函数中初始化DispatcherHelper

public App()    {        DispatcherHelper.Initialize();    }

DispatcherHelper主要成员以下:

  • UIDispatcher 属性:当DispatcherHelper调用Initialize方法时,使用当前线程的Dispatcher
  • Initialize 方法:初始化DispatcherHelper,并保存当前线程的Dispatcher
  • CheckBeginInvokeOnUI 方法:如果当前线程为非UI线程,那么在UI线程异步执行该方法,如果为UI
    线程,那么立即执行
  • Reset 方法:重置Dispatcher为Null
  • RunAsync 方法:在UI线程上异步执行

所以,刚才增加学生出错的代码,我们可以修改为

AddStudentCommand = new RelayCommand(() =>          {              Task.Factory.StartNew(() =>              {                  DispatcherHelper.CheckBeginInvokeOnUI(() =>                  {                      Teacher.Students.Add(new Student()                      {                          Name = "LaoLi",                          Age = 25                      });                  });              });          });

当然,直接调用那是最好,这里只是模拟非UI线程调用的情况。

转载于:https://www.cnblogs.com/HelloMyWorld/p/4750057.html

你可能感兴趣的文章
用 JavaScript 实现链表操作 - 11 Alternating Split
查看>>
Laravel优秀扩展包整理
查看>>
日志分析之识别真假蜘蛛与处理办法
查看>>
太多脚本将会毁掉持续交付
查看>>
一地鸡毛 OR 绝地反击,2019年区块链发展指南
查看>>
卢森堡大学发布RepuCoin系统,可破解区块链51%攻击
查看>>
国内云计算厂商众生相:四大阵营十几家企业生存盘点
查看>>
细说Unicode(一) Unicode初认识
查看>>
Node.js有了新的管理者
查看>>
Java 20年:历史与未来
查看>>
彻底理解Javascript中的原型链与继承
查看>>
腾讯最大规模裁撤中层干部,让贤年轻人
查看>>
gRPC-Web发布,REST又要被干掉了?
查看>>
如何:强化 TCP/IP 堆栈安全
查看>>
Spring3 MVC中使用Swagger生成API文档
查看>>
FastCGI PHP on Windows Server 2003
查看>>
LimeSDR Getting Started Quickly | LimeSDR上手指南
查看>>
JSP标签JSTL的使用(1)--表达式操作
查看>>
SAP顾问的人脉比技术更为重要
查看>>
FI/CO PA考试试卷
查看>>