提到这个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或者查阅国外相关的专题。
联系客服