2012-04-05 13 views
18

Sto utilizzando un controllo visivo nel mio progetto che proviene da una libreria a cui non dispongo dell'origine.
Ci vuole troppo tempo per aggiornare (200ms, approssimativamente) per una buona reattività dell'interfaccia utente con tre di questi controlli sullo schermo contemporaneamente. (Potrei aver bisogno di aggiornarli tutti e tre contemporaneamente, il che lascia la mia UI bloccata per ~ 600ms mentre stanno pensando tutti).Esecuzione di un controllo WPF in un'altra discussione

Ho letto alcuni post su TaskSchedulers e sto iniziando a esaminare le funzionalità di attività parallele come modo di eseguire ognuno di questi controlli nella loro stessa discussione. La piattaforma sarà multi-core, quindi voglio approfittare dell'elaborazione simultanea.

Il problema è che io non so nemmeno quello che non so come fare per questo, anche se ..

v'è un modello di design adatto alla corsa un controllo in un thread separato dal thread principale dell'interfaccia utente in WPF?

In particolare: è un controllo mappa di terze parti, che quando viene assegnata una nuova posizione o livello di zoom impiega troppo tempo per ridisegnare (~ 200 ms). Con forse tre di questi aggiornamenti ad un massimo di 4Hz - ovviamente non manterranno il passo ..
Ho incapsulato il controllo WPF in un controllo utente, e ho bisogno di eseguire ogni istanza nel proprio thread, mentre continuo a catturare l'input dell'utente (clic del mouse, ad esempio).

UPDATE: mentre mi sento in giro per una soluzione, ho implementato quanto segue finora.
Il mio thread principale (UI) genera un thread che crea una nuova finestra che contiene il controllo in questione e lo individua nella posizione corretta (in modo che assomigli a un normale controllo).

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

Dove CommandQueue è una coda Thread-safe BlockingCollection per inviare comandi alla mappa (spostando la posizione, ecc).
Il problema è ora che posso sia

  • avere input dell'utente dovuto al System.Windows.Threading.Dispatcher.Run() chiamata
  • o blocco sul CommandQueue, ascolto dei comandi inviati dal filo principale

I non posso girare in attesa di comandi, perché assorbirebbe tutta la mia CPU di thread!
È possibile bloccare e che la pompa di segnalazione eventi funzioni?

+0

controlli WPF Tutti devono essere aggiornati sul thread dell'interfaccia utente. Tuttavia, potremmo essere in grado di aiutarti se fornisci alcuni dettagli del controllo che stai utilizzando e qualsiasi codice che hai scritto per aggiornarlo/popolarlo. –

+0

@ CameronPeters, sei sicuro che non ci possa essere più di un "thread UI"? – svick

+0

Attualmente ho più thread "UI" in esecuzione al momento (grazie a Threading.Dispatcher.Run()), ma non posso bloccarli in attesa di segnali. – DefenestrationDay

risposta

11

Beh, ho un metodo che funziona - ma potrebbe non essere il più elegante ..

ho una finestra che contiene la mia terza parte (slow-rendering) controllo nel XAML.

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

    // ... 

} 

miei principali (UI) costrutti filo e inizia questa finestra su un thread:

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

Allora ottengo un handle alla finestra nel thread (dopo che è inizializzato):

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

.. e dopo aver messo un comando sulla coda di thread-safe che è condivisa con la finestra filettata:

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

.. ho lasciato so che può controllare la coda:

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

La finestra può ricevere il mio messaggio perché è agganciato nei messaggi di finestra:

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

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

..che poi può controllare:

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

.. e quindi eseguire sul thread!

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

facile come quello .. :)

+1

in realtà è un ottimo lavoro che hai fatto ... questo genere di cose sono raramente apprezzato qui –

5

Ho cercato in questo pure, e le informazioni più rilevanti ho trovato è stato in questo post del blog (ma non l'ho ancora provato):

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

si crea un hostVisual sul thread dell'interfaccia utente, quindi gira un thread in background, crea un oggetto MediaElement, lo inserisce in un oggetto VisualTarget (che punta a HostVisual) e lo inserisce all'interno della nostra hacking VisualTargetPresentationSource.

Il problema con questo metodo è che apparentemente l'utente non sarà in grado di interagire con i controlli in esecuzione nel nuovo thread.

Problemi correlati