定义
- WPF处理多线程的一种方式。当我们打开WPF应用程序即开启了一个进程,该进程中至少包含两个线程。
两种主要线程
- MSDN上对这两个线程是这样描述的:“Typically, WPF applications start with two threads: one for handling rendering and another for managing the UI. The rendering thread effectively runs hidden in the background while the UI thread receives input, handles events, paints the screen, and runs application code. Most applications use a single UI thread, although in some situations it is best to use several.”
- UI线程:接收输入、处理事件、绘制屏幕以及运行应用程序代码。即UI线程,也就是我们经常说的主线程。WPF要求将其大多数对象与该UI线程进行关联,称之为线程关联,意味着要使用一个WPF对象,只能在创建它的线程上使用,在其他线程上使用它会导致引发运行时异常
线程关联由Dispatcher类处理,该类即是用于WPF应用程序的,按优先级排列的消息循环。通常,WPF项目有单个Dispatcher对象(因此有单个UI线程),所有用户界面工作均以其为通道。
- 呈现线程:用于处理呈现,隐藏在后台运行
作用
背景
不管是WinForm应用程序还是WPF应用程序,实际上都是一个进程,一个进程可以包含多个线程,其中有一个是主线程,其余的是子线程。在WPF或WinForm应用程序中,主线程负责接收输入、处理事件、绘制屏幕等工作,为了使主线程及时响应,防止假死,在开发过程中对一些耗时的操作、消耗资源比较多的操作,都会去创建一个或多个子线程去完成操作,比如大数据量的循环操作、后台下载。这样一来,由于UI界面是主线程创建的,所以子线程不能直接更新由主线程维护的UI界面。子线程不能直接修改UI线程,必须通过向UI线程中的Dispatcher注册工作项来完成。在操作完成后,通过向UI线程的Dispatcher列队注册工作项,来通知UI线程更新结果。
作用
- Dispatcher的作用是用于管理线程工作项队列
- Dispatcher负责检测访问对象的线程与对象创建线程是否一致,不一致则抛出异常。
Dispatcher继承关系
- 在 WPF 的类层次结构中,大部分都集中派生于 DispatcherObject 类(通过其他类)。如下图 所示,您可以看到 DispatcherObject 虚拟类正好位于 Object 下方和大多数 WPF 类的层次结构之间。 要了解他们之间的关系可以参看下面这张类继承关系图:
Dispatcher详情
- 在 WPF 中绝大部分控件都继承自 DispatcherObject,甚至包括 Application。这些继承自 DispatcherObject 的对象具有线程关联特征,也就意味着只有创建这些对象实例,且包含了 Dispatcher 的线程(通常指默认 UI 线程)才能直接对其进行更新操作。
- DispatcherObject 类有两个主要职责:提供对对象所关联的当前 Dispatcher 的访问权限,以及提供方法以检查 (CheckAccess) 和验证 (VerifyAccess) 某个线程是否有权访问对象(派生于 DispatcherObject)。CheckAccess 与 VerifyAccess 的区别在于 CheckAccess 返回一个布尔值,表示当前线程是否可以使用对象,而 VerifyAccess 则在线程无权访问对象的情况下引发异常。通过提供这些基本的功能,所有 WPF 对象都支持对是否可在特定线程(特别是 UI 线程)上使用它们加以确定。如下图。
- Dispatcher
Dispatcher使用
- 下面我们来用一个实例,来看看如何正确从一个非 UI 线程中更新一个由UI线程创建的对象。
错误的更新方式
<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();
}
}
}
错误截图
- 正确的更新方式
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();
}