Application.Current.Dispatcher介绍
微软在WPF引入了Dispatcher,那么这个Dispatcher的主要作用是什么呢?
在WPF(Windows Presentation Foundation)应用程序中,Application.Current.Dispatcher
提供了一个对当前应用程序的调度器(Dispatcher)的引用。调度器是WPF中用于管理线程上执行的任务的关键组件,特别是对于UI线程上的操作至关重要。它允许你在UI线程上安全地执行代码,这对于更新UI元素(因为只有创建UI元素的线程才能修改它们)特别有用,尤其是在多线程环境中。
Dispatcher 是 WPF 中实现线程同步和异步调用的核心机制之一,它基于 .NET 的 System.Windows.Threading
命名空间。每个 WPF 窗口(Window
)都有一个与其关联的 Dispatcher
,负责管理该窗口所在的 UI 线程的消息队列。当您在 WPF 应用程序中进行任何 UI 相关的操作(如更新控件属性、触发事件等),这些操作必须在创建该控件的 UI 线程(也称为“主线程”或“UI 线程”)上执行。这是 Windows 操作系统对 UI 线程安全性的要求。
Dispatcher的作用是用于管理线程工作项队列。
主线程负责接收输入、处理事件、绘制屏幕等工作,这样一来,UI界面是主线程创建的,因为子线程不能直接更新由主线程维护的UI界面,所有调用Dispatcher更新UI。
不管是WinForm应用程序还是WPF应用程序,实际上都是一个进程,一个进程可以包含多个线程,其中有一个是主线程,其余的是子线程。在WPF或WinForm应用程序中,主线程负责接收输入、处理事件、绘制屏幕等工作,为了使主线程及时响应,防止假死,在开发过程中对一些耗时的操作、消耗资源比较多的操作,都会去创建一个或多个子线程去完成操作,比如大数据量的循环操作、后台下载。这样一来,由于UI界面是主线程创建的,所以子线程不能直接更新由主线程维护的UI界面。
Dispatcher的作用是用于管理线程工作项队列,类似于Win32中的消息队列,Dispatcher的内部函数,仍然调用了传统的创建窗口类,创建窗口,建立消息泵等操作。Dispatcher本身是一个单例模式,构造函数私有,暴露了一个静态的CurrentDispatcher方法用于获得当前线程的Dispatcher。对于线程来说,它对Dispatcher是一无所知的,Dispatcher内部维护了一个静态的 List<Dispatcher> _dispatchers, 每当使用CurrentDispatcher方法时,它会在这个_dispatchers中遍历,如果没有找到,则创建一个新的Dispatcher对 象,加入到_dispatchers中去。Dispatcher内部维护了一个Thread的属性,创建Dispatcher时会把当前线程赋值给这个 Thread的属性,下次遍历查找的时候就使用这个字段来匹配是否在_dispatchers中已经保存了当前线程的Dispatcher。
使用Thread
new Thread(()=>{
this.Dispatcher.Invoke(new Action(()=>{
//通知主线程去完成更新
}));
}).Start();
使用Invoke和BeginInvoke
- Invoke: 立即在UI线程上执行指定的委托,并且会阻塞调用线程直到操作完成。
Application.Current.Dispatcher.Invoke(new Action(() => {
Application.Current.MainWindow.Title = "我修改过的窗体标题";
}));
- BeginInvoke: 安排委托在UI线程上异步执行,不会阻塞当前线程。
var task = Application.Current.Dispatcher.BeginInvoke(new Action(() => {
Application.Current.MainWindow.Title = "我修改过的窗体标题";
}));
task.Completed += new EventHandler(task_Completed);
static void task_Completed(object sender, EventArgs e)
{
MessageBox.Show("任务已经完成");
}
使用task
在后台线程上执行完操作后更新UI
当你在后台线程执行耗时操作并需要在操作完成后更新UI时,可以使用调度器来安排UI更新任务。
private void UpdateUI(string result)
{
// 更新UI元素,例如一个TextBlock
this.textBlock1.Text = result;
}
private async void Button_Click(object sender, RoutedEventArgs e)
{
// 启动后台任务
Task<string> task = Task.Run(() => LongRunningOperation());
// 等待任务完成,并在UI线程上更新UI
string result = await task;
Application.Current.Dispatcher.Invoke(() => UpdateUI(result));
}
CheckAccess 和 VerifyAccess
CheckAccess
和 VerifyAccess
方法都是与WPF的Dispatcher对象相关的方法,用来帮助确保代码在正确的线程上下文中执行,特别是在涉及到UI更新的操作时。它们主要用于线程安全的检查,但两者的行为略有不同:
- 使用
CheckAccess
可以让你根据检查结果决定接下来的逻辑,适用于需要条件判断的场景。 - 使用
VerifyAccess
则是一种更严格的检查方式,适合那些必须在特定线程上执行的代码块,如果不满足条件则直接抛出异常。这种方式可以强制代码的正确性,但需要妥善处理可能抛出的异常。
CheckAccess
CheckAccess
方法用于检查当前代码是否正在UI线程(也就是创建Dispatcher对象的线程)上执行。它返回一个布尔值:
- 如果当前代码在UI线程上执行,返回
true
。 - 如果不在UI线程上,则返回
false
。
这个方法通常用于决定是否需要通过调度器来安排UI更新,或者直接执行操作。
if (Application.Current.Dispatcher.CheckAccess())
{
// 当前线程是UI线程,可以直接操作UI元素
myTextBox.Text = "直接更新";
}
else
{
// 不是UI线程,需要调度到UI线程执行
Application.Current.Dispatcher.Invoke(() => { myTextBox.Text = "通过调度器更新"; });
}
VerifyAccess
VerifyAccess
方法同样用于检查当前代码是否在创建Dispatcher对象的线程上执行。但是,与CheckAccess
不同的是,当检测到调用不在正确的线程上时,VerifyAccess
会抛出一个 InvalidOperationException
异常,而不是简单地返回 false
。
这个方法常用于在需要确保执行环境正确的方法或属性访问之前进行验证,以强制执行线程安全规则。
try
{
Application.Current.Dispatcher.VerifyAccess();
// 确保此处的代码在UI线程上执行,否则将抛出异常
someUIOperation();
}
catch (InvalidOperationException)
{
// 处理错误情况,比如通过调度器重新安排操作
}
小结
使用 Application.Current.Dispatcher
主要有以下意义:
- 确保 UI 线程安全:WPF 控件只能由创建它们的 UI 线程修改。通过
Dispatcher
,您可以安全地从其他线程访问和更新 UI,避免因非法跨线程操作引发的异常。 - 优化应用性能:通过异步调用(如
BeginInvoke
),可以避免阻塞 UI 线程,保持界面响应流畅,提升用户体验。 - 简化多线程编程:
Dispatcher
提供了一种统一且易于使用的机制来处理线程间通信和同步,简化了 WPF 应用程序的多线程开发工作。
在WPF中,所有的WPF对象都派生自DispatcherObject,DispatcherObject暴露了Dispatcher属性用来取得创建 对象线程对应的Dispatcher。DispatcherObject对象只能被创建它的线程所访问,其他线程修改 DispatcherObject需要取得对应的Dispatcher,调用Invoke或者BeginInvoke来投入任务。Dispatcher的一些设计思路包括 Invoke和BeginInvoke等从WinForm时代就是一直存在的,只是使用了Dispatcher来封装这些线程级的操作。
参照连接: