在VSTO中使用WPF进行用户交互时的线程设置
许多.NET桌面开发者习惯使用WPF开发用户界面。然而,在VSTO应用程序中使用WPF时,与传统桌面开发相比,必须正确设置线程以防止UI阻塞。 在传统桌面开发中,UI线程即是主线程。因此,我们通常在主线程中实例化Window并调用Show()方法来显示Window。但在VSTO应用程序中,主线程是VSTO_Main线程, Office外接程序将在VSTO_Main中通过COM组件访问Office中的数据以及执行操作。如果在该线程中实例化Window,那么所有用户与Window的交互及程序对COM组件的调用都将共享此线程。试想一下,如果我们打算在Window中展示Office中已有的数据,并在加载数据过程中显示等待动画,此时Window和数据读取程序将轮流使用VSTO_Main线程。当读取数据时,等待动画必须等待数据读取完成才能刷新界面,所以我们会看到等待动画卡顿。 为了解决这个问题,开发者可能会尝试将所有对COM组件的调用放在TaskPool中。然而,由于Office应用程序的单线程单元(STA)特性,在后台线程中调用COM组件会显著影响性能。例如,在一个程序中,需要读取Visio页面中的约300个形状并显示在UI中。如果在VSTO_Main线程中执行读取操作,约需10秒钟;而在后台线程中执行则需要约2分钟。这是因为后台线程在访问COM组件时,需要进行对象封送处理,极大降低了效率。 因此,最佳的解决方案是为WPF页面创建独立的线程。这可以通过以下步骤实现: 创建新线程:在新线程中实例化WPF窗口。 设置线程属性:确保新线程为STA线程,并启动消息循环。 管理线程间通信:通过Dispatcher将COM相关操作放在VSTO_Main中进行。 这样可以避免在主线程中进行繁重的UI操作,同时保持对COM组件的高效访问。 public partial class ThisAddIn { public static Dispatcher? VSTODispatcher { get; private set; } public static Dispatcher? UIDispatcher { get; private set; } private void ThisAddIn_Startup(object sender, System.EventArgs e) { // 记录下VSTO_Main线程的Dispatcher,以方便将相COM操作放在VSTO_Main中执行 VSTODispatcher = Dispatcher.CurrentDispatcher; // declare a UI thread to display wpf window var uiThread = new Thread(()=>{ // 记录下UI线程的Dispatcher,以方便在VSTO_Main线程中更新UI UIDispatcher = Dispatcher.CurrentDispatcher; var window = new MainWindow(); window.Show(); // 开启消息循环 System....