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<Student> _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<Student> 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

    <Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions> <StackPanel Grid.Column="0">
<TextBlock Text="{Binding Teacher.Name}"></TextBlock>
<TextBlock Text="{Binding Teacher.Age}"></TextBlock>
<ListView ItemsSource="{Binding Teacher.Students}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding Name}"></GridViewColumn>
<GridViewColumn DisplayMemberBinding="{Binding Age}"></GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</StackPanel> <StackPanel Grid.Column="1">
<Button Content="改变教师名称" Command="{Binding ChangeTeacherNameCommand}"></Button>
<Button Content="增加学生" Command="{Binding AddStudentCommand}"></Button>
<Button Content="改变最后一名学生名称" Command="{Binding ChangeLastStudentNameCommand}"></Button>
</StackPanel>
</Grid>

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;
} /// <summary>
/// Initializes a new instance of the MainViewModel class.
/// </summary>
public MainViewModel()
{
Teacher = new Teacher()
{
Name = "LaoZhao",
Age = 30,
Students = new ObservableCollection<Student>()
{
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线程调用的情况。

DispatcherHelper的更多相关文章

  1. MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息

    MVVM模式解析和在WPF中的实现(六) 用依赖注入的方式配置ViewModel并注册消息 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二 ...

  2. Win10 UWP开发系列——开源控件库:UWPCommunityToolkit

    在开发应用的过程中,不可避免的会使用第三方类库.之前用过一个WinRTXamlToolkit.UWP,现在微软官方发布了一个新的开源控件库—— UWPCommunityToolkit 项目代码托管在G ...

  3. MvvmLight ToolKit 教程

    MvvmLightToolKit MvvmLightToolKit的源代码是托管在CodePlex上的,我们 可以从这里获取最新版本的源代码,不仅源码,版本发布的日志,更改了哪些,修复了哪些,以及一些 ...

  4. 【MVVMLight小记】一.快速搭建一个基于MVVMLight的silverlight小程序

    写了篇MVVM小记http://www.cnblogs.com/whosedream/p/mvvmnote1.html,说好要写点MVVMLight的东西,所以接着写,以便和大家共勉. 我假设你已经有 ...

  5. 说不尽的MVVM(2) – MVVM初体验

    知识预备 阅读本文,我假定你已经具备以下知识: C#.WPF基础知识 了解Lambda表达式和TPL 对事件驱动模型的了解 知道ICommand接口 发生了什么 某程序员接到一个需求,编写一个媒体渲染 ...

  6. MvvmLight框架使用入门(一)

    MvvmLight是比较流行的MVVM框架,相对较为简单易用.可能正因为简单,对应的帮助文档不多,对初学者就不够友好了.这里会用几篇随笔,就个人对MvvmLight的使用经验,来做一个入门的介绍. 第 ...

  7. WPF Binding INotifyPropertyChanged 多线程 深入理解

    例子 先来看一个例子 Person.cs public class Person : ObservableObject,INotifyPropertyChanged { private string ...

  8. WPF ICommand 用法

    基础类,继承与ICommand接口 using System; using System.Collections.Generic; using System.Linq; using System.Te ...

  9. MVVMLight Toolkit在Windows Phone中的使用扩展之一:在ViewModel中实现导航,并传递参数

    MVVMLight是MVVM开发模式在Windows Phone平台下的一个开发框架,关于MVVMLight的基础使用,已经有人写的很好了,可以参照:MVVM.MVVMLight.MVVMLight ...

随机推荐

  1. CYQ.Data 数据框架 使用篇一 入门指南

    快速使用帮助 | 回贴(13) | 浏览(11303) | 发表日期 :2010-12-20 20:12:29   #楼主   本文针对V5版本进行修改于(2016-07-04) 下面是使用步骤: 一 ...

  2. NuGet及快速安装使用

    关于NuGet园子里已经有不少介绍及使用经验,本文仅作为自己研究学习NuGet一个记录. 初次认识NuGet是在去年把项目升级为MVC3的时候,当时看到工具菜单多一项Library Package M ...

  3. java容器(java编程思想第四版-读书笔记)

    容器类库图    List(interface) 次序是List最重要的特点:它保证维护元素特定的顺序.List为Collection添加了许多方法,使得能够向List中间插入与移除元素.(这只推荐L ...

  4. X264库直接压缩BITMAP格式数据

    最近帮朋友看了下X264压缩视频,主要参考了雷霄骅(leixiaohua1020)的专栏的开源代码: http://blog.csdn.net/leixiaohua1020/article/detai ...

  5. android 导入数据(通讯录)

    接着android 中导出数据 一文,下面介绍在android中导入数据的思路: 1.将数据从文本中读取出来 2.封装处理成自己想要的对象或模型 3.将处理好的数据对象插入自己应用的数据库中 4.更新 ...

  6. vs2008环境nmake编译 apache 2.2.29 openssl 1.0.1g mod_ssl 不知道如何生成“"..\..\srclib\openssl\inc32\openssl\store.h"”

    问题: vs2008环境nmake编译 apache 2.2.29 openssl 1.0.1g mod_ssl 不知道如何生成“"..\..\srclib\openssl\inc32\op ...

  7. java 生产者消费者问题 并发问题的解决

    引言 生产者和消费者问题是线程模型中的经典问题:生产者和消费者在同一时间段内共用同一个存储空间,如下图所示,生产者向空间里存放数据,而消费者取用数据,如果不加以协调可能会出现以下情况: 生产者消费者图 ...

  8. 浅谈输入输出”重定向“——基于Linux系统

    前言 进程在启动后会自动的打开3个文件:标准输入.标准输出和标准错误输出分别对应文件描述符0.1.2.对于每个进程他们都都维护了一张文件描述符表(file descriptor table),通常fd ...

  9. JAVA刷CSDN博客流量的思路

    先来看看CSDN统计博客访问量的原理: 当客户端向服务器发起请求的时候,每个ip被统计一次,上图的三个客户端指的是三个不同的ip地址.并且在某段时间内,同以ip访问同一篇文章,只能算是一次访问量.同一 ...

  10. Windows 批处理(bat)语法大全

    本文是学习bat整理的笔记,由于内容较多,建议结合右侧文章大纲查看. %~dp0[获取当前路径] %~dp0 “d”为Drive的缩写,即为驱动器,磁盘.“p”为Path缩写,即为路径,目录 cd % ...