WPF常用技巧-多线程处理
WPF支持单线程单元模型,该模型与在Windows窗体应用程序中使用的模型非常类似,具有以下几条原则:
- WPF元素具有线程关联性。创建WPF元素的线程拥有所创建的元素,其他线程不能直接与这些WPF元素进行交互。
- WPF对象都在类层次的某个位置继承自
DispatcherObject
类,DispatcherObject
类提供了少量成员,用于核实访问WPF对象的代码是否在正确的线程上执行,如果没有,是否能切换位置。
Dispatcher类
Disparcher
类的实例为一个调度程序,管理在WPF应用程序中发生的操作。调度程序拥有应用程序线程(也就是拥有线程中创建的WPF元素),并管理工作项队列,当应用程序运行时,调度程序接受新的工作请求,并且一次执行一个任务。
在WPF中,有一个基类DisparcherObject
,所有WPF组件如Window
、Button
等都继承自DispatcherObject
,当某个线程中第一次实例化DisparcherObject
的子类时,会创建一个调度程序。因此,如果开多个线程,每个线程都展示独立的窗体,那么将会创建多个调度程序。但在一般情况下,开发应用程序只使用一个用户界面线程和一个调度程序。
- 注意区分,
Dispatcher
与DispatcherObject
并不是父子类。
DispatcherObject类
一、常用成员
Dispatcher
:属性成员,返回管理该对象的调度程序。
CheckAccess()
:如果代码在正确的线程上使用对象,就返回true
,否则返回false
。
VerifyAccess()
:如果代码在正确的线程上使用对象,就什么也不做,否则抛出InvalidOperationException
异常。
- 一般情况下我们不需要自己去调用
VerifyAccess()
方法,WPF对象为保护自身会频繁调用VerifyAccess()
方法,从而不可能在错误的线程中长时间使用一个对象。
在需要跨线程访问控件时,可以通过控件的调度程序,即Dispatcher
对象的Invoke()
或BeginInvoke()
方法来将代码安排为调度程序的任务,然后控件的调度程序会去执行这些代码。
BeginInvoke(DispatcherPriority priority, Delegate method)
:第一个参数指示任务的优先级,为DispatcherPriority
枚举类型,一般情况下使用DispatcherPriority.Normal
即可,如果任务不需要被立即完成,也可以使用更低的优先级;第二个参数为一个方法的委托Delegate
类型,该委托指向具体任务的方法。
DispatcherPriority.ApplicationIdle
:等待应用程序在完成所有其他工作时执行指定的任务。DispatcherPriority.SystemIdle
:比ApplicationIdle
优先级更低,直到整个系统都处于休息状态,并且CPU处于空闲状态才执行。
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Run(() =>
{
Change();
});
}
private void Change()
{
if (!CheckAccess())
{
Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() =>{
lbl_Test.Content = "Test-ChangeOne";
})); ;
}
else
{
lbl_Test.Content = "Test-ChangeTwo";
}
}
Invoke()
:Invoke()
函数的参数是一个Action
或Func
类型对象,与BeginInvoke
的区别是,BeginInvoke
是异步执行的,Invoke
同步执行的,使用Invoke
时,如果执行任务比较耗时,会导致UI界面卡死。
private void Button_Click(object sender, RoutedEventArgs e)
{
Task.Run(() =>
{
Change();
});
}
private void Change()
{
if (!CheckAccess())
{
Dispatcher.Invoke(DispatcherPriority.Normal, new Action((
{
Thread.Sleep(5000);//这里做延时,会发现UI界面卡住
lbl_Test.Content = "Test-ChangeOne";
}));
}
else
{
lbl_Test.Content = "Test-ChangeTwo";
}
}
二、Dispacher调度器对象的获取
常见的获取Dispacher
调度器的方式有如下三种:
直接调用Dispatcher属性
由于WPF中的绝大多是类型都是DispatcherObject
的子类,因此继承了Dispatcher
属性,可以直接在类中通过Dispatcher
来获取。(视图的后台代码继承了Window
或UserControl
等都是DispatcherObject
的子类)
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Dispatcher.BeginInvoke(() =>
{
});
}
}
通过Dispatcher的静态属性
通过System.Windows.Threading
命名空间下的Dispatcher
属性可以获得当先线程的调度程序对象。
- 注意,这里是获取当前线程的调度器对象,并不一定能获得UI的调度器对象。
var dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher;
通过Application获取
如果是在应用程序中,如WPF,可以通过Application.Current.Dispatcher
来获取当前UI线程的调度程序对象。
- 这里可以直接获取到当前应用的UI调度器对象
var dispatcher = Application.Current.Dispatcher;
DispatcherTimer
在WPF中常常会遇到按照一定间隔时间执行同一个任务的场景,这个时候就可以使用定时器DispatcherTimer
来进行定时任务的设定了。
DispatcherTimer
执行任务的线程是在UI调度器所在线程上,所以可以在执行任务中直接访问和操作UI元素,而不会引发线程安全问题。
常用的两种创建方式:
//第一种
private DispatcherTimer? _timer;
private void MyTask(){ ... }
public MainWindowViewModel()
{
_timer = new DispatcherTimer(TimeSpan.FromSeconds(1), DispatcherPriority.Loaded,
new EventHandler((s, e) => MyTask()), Application.Current.Dispatcher);
}
//第二种
private DispatcherTimer _timer = new DispatcherTimer();
private void MyTask(object? sender, EventArgs e) { ... }
public MainWindowViewModel()
{
_timer.Interval = TimeSpan.FromSeconds(1);
_timer.Tick += MyTask;
}
计时器的开始与停止:
_timer?.Start();
_timer?.Stop();
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!