2012-04-05 23 views
18

Estoy usando un control visual en mi proyecto que proviene de una biblioteca a la que no tengo acceso.
Lleva mucho tiempo actualizar (200ms, aproximadamente) para una buena respuesta de la IU con tres de estos controles en pantalla a la vez. (Es posible que deba actualizar los tres a la vez, lo que deja mi UI atascada durante ~ 600 ms mientras todos están pensando).Ejecutar un control WPF en otro hilo

He leído algunas publicaciones sobre TaskSchedulers, y estoy comenzando a investigar las funciones de la tarea Paralela como una forma de ejecutar cada uno de estos controles en su propio hilo. La plataforma será multinúcleo, por lo que quiero aprovechar el procesamiento simultáneo.

El problema es que ni siquiera sé lo que no sé acerca de cómo hacer esto, aunque ..

¿Existe un patrón de diseño apropiado para ejecutar un control en un hilo separado de la principal UI hilo en WPF?

Específicamente: es un control de mapa de terceros, que cuando se le da una nueva ubicación o el nivel de zoom tarda demasiado en volver a dibujar (~ 200ms). Con quizás tres de estas actualizaciones a un máximo de 4Hz, obviamente no se mantendrán al día.
He encapsulado el control WPF en un control de usuario, y necesito ejecutar cada instancia en su propio hilo, mientras sigo capturando la entrada del usuario (clics del mouse, por ejemplo).

ACTUALIZACIÓN: mientras estoy buscando una solución, he implementado lo siguiente hasta ahora.
Mi hebra principal (UI) genera un hilo que crea una nueva ventana que contiene el control en cuestión, y lo ubica en la posición correcta (para que parezca que es solo un control normal).

_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(); 

Dónde CommandQueue es una cola Thread-safe BlockingCollection para enviar comandos al mapa (que se mueve la ubicación, etc).
El problema ahora es que puedo ya sea

  • tener la entrada del usuario debido a la System.Windows.Threading.Dispatcher.Run() llamada
  • o bloque en el CommandQueue, la escucha de los comandos enviados por el hilo principal

I no puedo girar esperando órdenes, ¡porque absorbería todo mi hilo CPU!
¿Es posible bloquear y tener activada la bomba de mensajes del evento?

+0

controles WPF Todos deben actualizarse en el hilo de interfaz de usuario. Sin embargo, podríamos ayudarlo si proporciona algunos detalles del control que está utilizando y cualquier código que haya escrito para actualizarlo/poblarlo. –

+0

@CameronPeters, ¿está seguro de que no puede haber más de un "hilo de interfaz de usuario"? – svick

+0

Actualmente tengo varios subprocesos 'UI' en ejecución en este momento (gracias a Threading.Dispatcher.Run()), pero no puedo bloquearlos en espera de señales. – DefenestrationDay

Respuesta

11

Bueno, tengo un método que funciona - pero también puede no ser el más elegante ..

tengo una ventana que contiene mi tercero (lento-rendering) de control en el XAML.

public partial class MapWindow : Window 
{ 
    private ConcurrentQueue<MapCommand> _mapCommandQueue; 
    private HwndSource _source; 

    // ... 

} 

Mis principales (UI) contructs hilo y comienza esta ventana en un hilo:

_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(); 

entonces consigo un identificador de la ventana en el hilo (después de que se ha inicializado):

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; 
    } 
} 

.. y después de poner una orden en la cola de flujos seguros que se comparte con la ventana de rosca:

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

.. I que éste sepa que puede comprobar la cola:

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

La ventana puede recibir mi mensaje porque se ha enganchado en los mensajes de ventana:

protected override void OnSourceInitialized(EventArgs e) 
{ 
    base.OnSourceInitialized(e); 

    _source = PresentationSource.FromVisual(this) as HwndSource; 
    if (_source != null) _source.AddHook(WndProc); 
} 

..which entonces puede verificar:

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; 
} 

..y luego ejecutar en el hilo!

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; 
     } 
    } 
} 

Tan fácil como eso .. :)

+1

es realmente un gran trabajo que hizo ... este tipo de cosas rara vez apreciado aquí –

5

He estado buscando en esto también, y la información más relevante que pude encontrar fue en esta entrada del blog (sin embargo no he probado todavía):

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

crea un HostVisual en el hilo de la interfaz de usuario, luego muestra un hilo de fondo, crea un elemento de medio, lo coloca dentro de un VisualTarget (que apunta a HostVisual) y lo coloca todo dentro de nuestro hacky VisualTargetPresentationSource.

El problema con este método es que aparentemente el usuario no podrá interactuar con los controles que se ejecutan en el nuevo subproceso.

Cuestiones relacionadas