在另一个线程中运行WPF控件

我在我的项目中使用了一个可视化控件,它来自一个我没有源代码的库。
更新(大约200毫秒)以获得良好的UI响应时间需要很长时间,其中三个控件同时在屏幕上显示。 (我可能需要一次更新所有三个,这使得我的UI在他们都在思考的时候停留了大约600毫秒)。

我已经阅读了一些关于TaskSchedulers的post,并开始研究并行任务function,作为在自己的线程中运行每个控件的一种方式。 该平台将是多核的,所以我想利用同步处理。

问题是我甚至不知道我不知道如何解决这个问题。

是否有合适的设计模式用于在WPF中的主UI线程的单独线程中运行控件?

具体来说 :它是第三方地图控件,当给定新位置或缩放级别时需要太长时间才能重绘(~200ms)。 或许其中有三个更新最高为4Hz – 显然他们无法跟上..
我已经在用户控件中封装了WPF控件,并且需要在它自己的线程中运行每个实例,同时仍然捕获用户输入(例如,鼠标点击)。

更新 :当我感觉到解决方案时,到目前为止我已经实现了以下内容。
我的主(UI)线程产生一个线程,该线程创建一个包含相关控件的新窗口,并将其定位在正确的位置(这样它看起来只是一个普通的控件)。

_leftTopThread = new Thread(() => { _topLeftMap = new MapWindow() { WindowStartupLocation = WindowStartupLocation.Manual, Width = leftLocation.Width, Height = leftLocation.Height, Left = leftLocation.X, Top = leftLocation.Y, CommandQueue = _leftMapCommandQueue, }; _topLeftMap.Show(); System.Windows.Threading.Dispatcher.Run(); }); _leftTopThread.SetApartmentState(ApartmentState.STA); _leftTopThread.IsBackground = true; _leftTopThread.Name = "LeftTop"; _leftTopThread.Start(); 

其中CommandQueue是一个线程安全的BlockingCollection队列,用于向地图发送命令(移动位置等)。
问题是现在我可以

  • 由于System.Windows.Threading.Dispatcher.Run()调用而具有用户输入
  • 或阻塞CommandQueue,监听主线程发送的命令

我无法等待命令,因为它会吸收我所有的线程CPU!
是否可以阻止并使事件消息泵工作?

好吧,我有一个有效的方法 – 但它可能不是最优雅的..

我有一个窗口,其中包含我在XAML中的第三方(慢速渲染)控件。

 public partial class MapWindow : Window { private ConcurrentQueue _mapCommandQueue; private HwndSource _source; // ... } 

我的主(UI)线程在一个线程上构造并启动这个窗口:

 _leftTopThread = new Thread(() => { _topLeftMap = new MapWindow() { WindowStartupLocation = WindowStartupLocation.Manual, CommandQueue = _leftMapCommendQueue, }; _topLeftMap.Show(); System.Windows.Threading.Dispatcher.Run(); }); _leftTopThread.SetApartmentState(ApartmentState.STA); _leftTopThread.IsBackground = true; _leftTopThread.Name = "LeftTop"; _leftTopThread.Start(); 

然后我获得了线程中窗口的句柄(在初始化之后):

 private IntPtr LeftHandMapWindowHandle { get { if (_leftHandMapWindowHandle == IntPtr.Zero) { if (!_topLeftMap.Dispatcher.CheckAccess()) { _leftHandMapWindowHandle = (IntPtr)_topLeftMap.Dispatcher.Invoke( new Func(() => new WindowInteropHelper(_topLeftMap).Handle) ); } else { _leftHandMapWindowHandle = new WindowInteropHelper(_topLeftMap).Handle; } } return _leftHandMapWindowHandle; } } 

..并在将命令放到与线程窗口共享的线程安全队列之后:

 var command = new MapCommand(MapCommand.CommandType.AircraftLocation, new object[] {RandomLatLon}); _leftMapCommendQueue.Enqueue(command); 

..我让它知道它可以检查队列:

 PostMessage(LeftHandMapWindowHandle, MapWindow.WmCustomCheckForCommandsInQueue, IntPtr.Zero, IntPtr.Zero); 

窗口可以接收我的消息,因为它已连接到窗口消息:

 protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); _source = PresentationSource.FromVisual(this) as HwndSource; if (_source != null) _source.AddHook(WndProc); } 

..然后它可以检查:

 private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) // { // Handle messages... var result = IntPtr.Zero; switch (msg) { case WmCustomCheckForCommandsInQueue: CheckForNewTasks(); break; } return result; } 

..然后在线程上执行!

 private void CheckForNewTasks() { MapCommand newCommand; while (_mapCommandQueue.TryDequeue(out newCommand)) { switch (newCommand.Type) { case MapCommand.CommandType.AircraftLocation: SetAircraftLocation((LatLon)newCommand.Arguments[0]); break; default: Console.WriteLine(String.Format("Unknown command '0x{0}'for window", newCommand.Type)); break; } } } 

很容易.. 🙂

我一直在研究这个,我能找到的最相关的信息是在这篇博文中(但我还没有测试过):

http://blogs.msdn.com/b/dwayneneed/archive/2007/04/26/multithreaded-ui-hostvisual.aspx

它在UI线程上创建一个HostVisual,然后旋转一个后台线程,创建一个MediaElement,将它放在一个VisualTarget(指向HostVisual)中,并将它们全部放在我们的hacky VisualTargetPresentationSource中。

这种方法的问题是显然用户将无法与新线程中运行的控件进行交互。