Подтвердить что ты не робот

Запуск элемента управления WPF в другом потоке

Я использую визуальный контроль в своем проекте, который из библиотеки, для которой у меня нет источника.
Требуется слишком много времени для обновления (примерно 200 мс) для хорошей реакции на пользовательский интерфейс с тремя из этих элементов управления на экране одновременно. (Мне может потребоваться обновить все три сразу, что оставляет мой пользовательский интерфейс застрял на ~ 600 мс, пока они все думают).

Я прочитал несколько сообщений о TaskSchedulers и начал изучать функции задач Parallel как способ запуска каждого из этих элементов управления в своем потоке. Платформа будет многоядерной, поэтому я хочу воспользоваться одновременной обработкой.

Проблема в том, что я даже не знаю, чего я не знаю о том, как это сделать, хотя..

Есть ли подходящий шаблон проектирования для запуска элемента управления в отдельном потоке из основного потока пользовательского интерфейса в WPF?

Конкретно: это элемент управления сторонней картой, когда при новом местоположении или уровне масштабирования требуется слишком много времени для перерисовки (~ 200 мс). Возможно, три из этих обновлений не превышают 4 Гц - очевидно, что они не ускорятся.
Я инкапсулировал элемент управления WPF в usercontrol и должен запускать каждый экземпляр в его собственном потоке, сохраняя при этом пользовательский ввод (например, щелчки мыши).

UPDATE: пока я чувствую себя в стороне от решения, я реализовал следующее. Мой основной (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 является Thread-safe BlockingCollection Очередь для отправки команд на карту (перемещение местоположения и т.д.).
Проблема в том, что я могу либо

  • имеет пользовательский ввод из-за вызова System.Windows.Threading.Dispatcher.Run()
  • или заблокировать CommandQueue, прослушивая команды, отправленные основным потоком

Я не могу отжимать ожидающие команды, потому что он впитал бы весь мой процессор потоков!
Можно ли заблокировать и запустить сообщение-насос события?

4b9b3361

Ответ 1

Ну, у меня есть метод, который работает - но он может быть не самым элегантным..

У меня есть окно, содержащее элемент управления третьей стороной (медленный рендеринг) в 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;
        }
    }
}

Просто как это..:)

Ответ 2

Я тоже изучал это, и самая важная информация, которую я мог найти, была в этом сообщении в блоге (однако я еще не тестировал его):

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

он создает HostVisual в потоке пользовательского интерфейса, затем закручивает фоновый поток, создает MediaElement, помещает его внутри VisualTarget (который указывает на HostVisual) и помещает все это в наш хакерский источник VisualTargetPresentation.

Проблема с этим методом заключается в том, что, по-видимому, пользователь не сможет взаимодействовать с элементами управления, которые работают в новом потоке.