18

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

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

问题是我什至不知道我不知道该怎么做,虽然..

是否有合适的设计模式可以在与 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!
是否可以阻止让事件消息泵工作?

4

2 回答 2

13

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

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

public partial class MapWindow : Window
{
    private ConcurrentQueue<MapCommand> _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<IntPtr>(() => 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;
        }
    }
}

就这么简单.. :)

于 2012-04-12T10:49:47.277 回答
6

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

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

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

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

于 2012-04-05T13:24:54.403 回答