最近遇到一个问题,在wpf程序的某个线程中打开子窗体时显示“调用线程必须为 STA,因为许多 UI 组件都需要”,这是典型的子线程更新UI异常问题了,解决方法是用Dispatcher的invoke方法来执行UI操作。
Dispatcher的字面意思是“调度员”,很形象地解释了它的作用——处理并发和多线程。Dispatcher本身是一个单例模式,构造函数私有,暴露了一个静态的CurrentDispatcher方法用于获得当前线程的Dispatcher,Dispatcher内部维护了一个静态的 List<Dispatcher> _dispatchers, 每当使用CurrentDispatcher方法时,它会在这个_dispatchers中遍历,如果没有找到,则创建一个新的Dispatcher对 象,加入到_dispatchers中去。Dispatcher内部维护了一个Thread的属性,创建Dispatcher时会把当前线程赋值给这个 Thread的属性,下次遍历查找的时候就使用这个字段来匹配是否在_dispatchers中已经保存了当前线程的Dispatcher。WPF的控件均继承自DispatcherObject,具有线程关联特征,包含了 Dispatcher 的线程(通常指默认 UI 线程)才能对UI控件进行操作。
回到问题,因为工作线程并不能操作UI,所以出错;这里需要用到Dispatcher的invoke方法或者BeginInvoke方法来解决多线程更新UI问题(Invoke是同步执行,BeginInvoke是异步执行);不过首先须要获取当前AppDomain的Application对象,然后获取与此 DispatcherObject 关联的 Dispatcher。
示例如下:
MainWindow.xaml:
<Window x:Class="DispatcherLearning.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:DispatcherLearning" Title="MainWindow" Height="325" Width="502"> <Grid> <Button Name="btn" Content="打开另一个窗体" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OpenWindow"/> </Grid> </Window>
后台代码:
private void OpenWindow(object sender, RoutedEventArgs e) { Thread thread = new Thread(() => { Application.Current.Dispatcher.Invoke(new Action(() => { ChildWindow.show("打开了一个新窗体!"); })); }); thread.IsBackground = true; thread.Start(); }
ChildWindow类:
public partial class ChildWindow : Window { private ChildWindow() { InitializeComponent(); } public static void show(string message) { ChildWindow childwin = new ChildWindow(); childwin.messageBlock.Text = message; childwin.ShowDialog(); } }
运行效果