Dispatcher及多线程
提到这个UI和后台线程交互这个问题,大家都可能在WinForm中遇到过,记得几年前我参加一个外资企业的面试,公司的其中一道题就是说在WinForm 中如何使用后台线程来操作UI,所以对这个问题比较记忆犹新。
WPF线程分配系统提供一个Dispatcher属性、VerifyAccess 和 CheckAccess 方法来操作线程。线程分配系统位于所有 WPF 类中基类,大部分WPF 元素都派生于此类,如下图的Dispatcher类:
WPF 应用程序启动后,会有两个线程:
- 一个是用来处理UI呈现(处理UI的请求,比如输入和展现等操作)。
- 一个用来管理 UI的 (对UI元素及整个UI进行管理)。
与 Dispatcher 调度对象想对应的就是 DispatcherObject,在 WPF 中绝大部分控件都继承自 DispatcherObject,甚至包括 Application。这些继承自 DispatcherObject 的对象具有线程关联特征,也就意味着只有创建这些对象实例,且包含了 Dispatcher 的线程(通常指默认 UI 线程)才能直接对其进行更新操作。
当我们尝试从一个非 UI 线程更新一个UI元素,会看到如下的异常错误。
XAML代码: <Window x:Class="WPFApplications.Window2" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="Window2" Height="300" Width="300"> <StackPanel> <Label x:Name="lblHello">Hello,World!</Label> </StackPanel> </Window> 后台代码: public partial class Window2 : Window { public Window2() { InitializeComponent(); Thread thread = new Thread(ModifyUI); thread.Start(); }
private void ModifyUI() { // 模拟一些工作正在进行 Thread.Sleep(TimeSpan.FromSeconds(5)); lblHello.Content = "Hello,Dispatcher"; } }
错误截图:
按照 DispatcherObject 的限制原则,我们改用 Window.Dispatcher.Invoke() 即可顺利完成这个更新操作。 private void ModifyUINew() { // 模拟一些工作正在进行 Thread.Sleep(TimeSpan.FromSeconds(5)); this.Dispatcher.BeginInvoke(DispatcherPriority.Normal,(ThreadStart)delegate() { lblHello.Content = "Hello,Dispatcher"; }); }
如果在其他工程或者类中,我们可以用 Application.Current.Dispatcher.Invoke方法来完成同样的操作,它们都指向 UI Thread Dispatcher这个唯一的对象。
Dispatcher 同时还支持 BeginInvoke 异步调用,如下代码: private void btnHello_Click(object sender, RoutedEventArgs e) { new Thread(() => { Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => { Thread.Sleep(TimeSpan.FromSeconds(5)); this.lblHello.Content = DateTime.Now.ToString(); })); }).Start(); } 关于Dispatcher和WPF多线程,还有很多要讲,由于篇幅有限且精力有限,我这里只讲一些我们最常见的应用,同时包括Freezable 的处理等问题,大家可以查阅MSDN或者查阅国外相关的专题。
|