> 文章列表 > WPF教程(四)--Dispatcher

WPF教程(四)--Dispatcher

WPF教程(四)--Dispatcher

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

二 、走进Dispatcher
所有 WPF 应用程序启动时都会加载两个重要的线程:一个用于呈现用户界面(隐藏线程--不操作),另一个用于管理用户界面(UI线程--操作)。呈现线程是一个在后台运行的隐藏线程,

因此您通常面对的唯一线程 就是 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>

后台代码:

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