WPF 常用技巧-多线程处理

200 阅读5分钟

前言

WPF支持单线程单元模型,该模型与在Windows窗体应用程序中使用的模型非常类似,具有以下几条原则:

WPF元素具有线程关联性。创建WPF元素的线程拥有所创建的元素,其他线程不能直接与这些WPF元素进行交互。

WPF对象都在类层次的某个位置继承自DispatcherObject类,DispatcherObject类提供了少量成员,用于核实访问WPF对象的代码是否在正确的线程上执行,如果没有,是否能切换位置。

Dispatcher类

Disparcher类的实例为一个调度程序,管理在WPF应用程序中发生的操作。调度程序拥有应用程序线程(也就是拥有线程中创建的WPF元素),并管理工作项队列,当应用程序运行时,调度程序接受新的工作请求,并且一次执行一个任务。

在WPF中,有一个基类DisparcherObject,所有WPF组件如WindowButton等都继承自DispatcherObject,当某个线程中第一次实例化DisparcherObject的子类时,会创建一个调度程序。因此,如果开多个线程,每个线程都展示独立的窗体,那么将会创建多个调度程序。但在一般情况下,开发应用程序只使用一个用户界面线程和一个调度程序。

注意区分,DispatcherDispatcherObject并不是父子类。

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()函数的参数是一个ActionFunc类型对象,与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来获取。(视图的后台代码继承了WindowUserControl等都是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();

最后

如果你觉得这篇文章对你有帮助,不妨点个赞支持一下!你的支持是我继续分享知识的动力。如果有任何疑问或需要进一步的帮助,欢迎随时留言。

也可以加入微信公众号 [DotNet技术匠] 社区,与其他热爱技术的同行一起交流心得,共同成长!

优秀是一种习惯,欢迎大家留言学习!

作者:SchuylerEX

出处:blog.csdn.net/jjailsa/category_12548807.html

声明:网络内容,仅供学习,尊重版权,侵权速删,歉意致谢!