WPf-Dispatcher常见使用场景
目录
Application.Current.Dispatcher(或者控件自带的 this.Dispatcher)的核心作用是:"任何非UI线程相碰UI使,都需要它"。除了 Task.Run ,还有几种常见场景也必须使用它,否则程序直接崩溃。
1.定时器(System.Timers.Timer)
.NET有好几种定时器
- DispatcherTimer:运行在UI线程(安全,不需要 Dispatcher)
- System.Timers.Timer: 运行在后台线程池(高效,但不安全)
如果为了性能使用了 System.Timers.Timer,在它的回调里想更新界面,就必须用Dispatcher。
// 初始化一个后台定时器
System.Timers.Timer _timer = new System.Timers.Timer(1000); // 1秒一次
public MainWindow()
{
InitializeCompoent();
_timer.Elapsed += Timer_Elapsed;
timer.start();
}
// 这个事件是在后台线程触发的
private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
// 错误:直接改 UI 会崩
// txtTime.Text = DataTime.Now.ToString();
// 正确
Application.Current.Dispatcher.Invoke(()=>
{
txtTime.Text = DataTime.Now.ToString();
});
}2.硬件或网络事件(串口,Socket,USB)
工控,物联网上位机开发中的常见场景。
当 SerialPort(串口)收到数据,或者 Socket 收到 TCP 包是,底层驱动会通过一个后台线程触发 DataReceived 事件。
// 串口对象
SerialPort _serialPort = new SerialPort("COM1");
public void Init()
{
_serialPort.DataReceived += SerialPort_DataReceived;
}
// 典型的后台线程事件
private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e)
{
string data = _serialPort.ReadExisting();
// 必须用 Dispatcher 把数据送到界面显示
Application.Current.Dispatcher.Invoke(() =>
{
txtLog.AppendText(data + "\n");
});
}3.文件监控(FileSystemWatcher)
如果你写一个软件监听某个文件夹,当有新文件生成时自动显示到列表里。FileSystemWatcher 的事件也是在后台线程触发的。
FileSystemWatcher watcher = new FileSystemWatcher(@"C"\Temp");
watcher.Created += (s, e) =>
{
// 这里是后台线程
Application.Current.Dispatcher.Invoke(() =>
{
// 更新 UI 列表
myListBox.Items.Add($"发现新文件:{e.name}");
});
};4.老式的Thread写法
虽然现在都用 Task,但是在维护老代码时可能会看到 new Thread()。这也属于后台线程。
Thread thread = new Thread(() =>
{
// 后台线程做旧式操作
Thread.Sleep(1000);
Application.Current.Dispatcher.Invoke(() =>
{
txtStatus.Text = "老线程干完了";
});
});
thread.Start();补充:**Application.Current.Dispatcher **和 this.Dispatcher 有啥区别?
你会发现有时候用 this.Dispatcher,有时候用 Application.Current.Dispatcher。
-
this.Dispatcher:- 含义:获取当前窗口(或控件)所关联的 UI 线程管家。
- 优点:多窗口程序时更准确(假如你开启了多个独立的 UI 线程窗口,极为罕见)。
- 限制:代码必须写在
Window或UserControl的后台代码里才能用this。
-
Application.Current.Dispatcher:- 含义:获取整个应用程序的主 UI 线程管家。
- 优点:全局通用。哪怕你在一个普通的
Person类、ViewModel类或者静态辅助类里,只要想更新主界面,都可以用它。 - 缺点:如果主窗口关闭了,有时候访问它可能会有问题(但在 WPF 生命周期内通常没事)。
总结
只要你的代码此时此刻不在 UI 线程(无论是 Task.Run 还是定时器,还是硬件事件),而你又想更新界面,就必须请出 Dispatcher。