Qual è un modello di progettazione efficace per "fan-in" da più thread di attività in un singolo thread dell'interfaccia utente?

0

In molte applicazioni il "thread UI" è speciale e gli aggiornamenti dell'interfaccia utente del framework devono essere inviati da questo thread.

Tuttavia, per evitare di bloccare l'interfaccia utente, le attività lente vengono eseguite nei thread in background e i risultati vengono propagati al thread dell'interfaccia utente per essere visualizzati.

Diversi quadri risolvono questo in modi leggermente diversi; ad esempio:

  • In WPF è possibile utilizzare Application.Current.Dispatcher per generare un'attività che viene eseguita nel thread dell'interfaccia utente.

  • Su Android, puoi utilizzare runOnUiThread per inviare un'azione alla coda degli eventi per il thread dell'interfaccia utente.

  • In QT si associa un gestore di eventi utilizzando il proprio sistema segnale / slot per ricevere notifiche dal thread in background e aggiornare utilizzando i gestori di eventi del thread UI.

  • In iOS utilizzi qualcosa come dispatch_async(dispatch_get_main_queue()... per attivare un'attività nella coda degli eventi dell'interfaccia utente.

Tutto leggermente diverso, ma in generale il modello è chiaro: il thread in background crea un Task che poi pianifica per essere eseguito su un TaskQueue che il thread UI periodicamente fornisce servizi in modo sincrono.

Tuttavia, non sono convinto che questa sia effettivamente la migliore pratica.

Certamente per pochi, grandi compiti in background è ragionevole.

... ma quando hai un grande fan-out in sotto-thread e una delega one-to-one di task-complete - > Compito dell'interfaccia utente, questo semplifica l'interfaccia utente con un sacco di piccoli compiti di aggiornamento, vanificando lo scopo di spostare il lavoro in thread di sfondi in primo luogo.

Sembra molto più vantaggioso avere un livello di aggregazione a livello di applicazione che combina e filtra gli aggiornamenti dell'interfaccia utente e trasmette solo i necessari attraverso il thread dell'interfaccia utente effettivo; in molti modi questo è effettivamente ciò che fa il 'Virtual DOM' (ma senza discussioni).

Quindi, c'è il caso d'uso.

Ecco la domanda:

  • Qual è un modello efficace per scrivere un tale livello di aggregazione di thread?

(... e si noti che poiché si tratta di un'operazione solo dati, non vi è alcun requisito che questo layer sia servito da un singolo thread come il loop di eventi del thread UI, ma potrebbe essere in effetti ottimale per il servizio in arrivo aggiornamenti tramite un pool di thread)

    
posta Doug 12.09.2017 - 09:19
fonte

2 risposte

1

In generale, è meglio iniziare a scrivere la tua domanda in modo ingenuo poiché nella maggior parte dei casi l'interfaccia utente rimarrà reattiva ed è più facile da mantenere. Tuttavia, ho incontrato situazioni in cui l'interfaccia utente si impantana con 1.000 "task" nella coda - la maggior parte dei quali sono duplicati. Succede abbastanza spesso in applicazioni quasi in tempo reale in cui si elaborano uno o più flussi di dati e li si visualizza sullo schermo.

Ho lavorato su un'applicazione del genere e la nostra scelta era di mantenere il flusso di dati separato dall'interfaccia utente. Ciò ha richiesto un po 'di preparazione:

  • Tutti gli oggetti dati necessari per essere immutabili o non notificare all'interfaccia utente che è stato modificato
  • Tutti gli aggiornamenti sono stati eseguiti su un orologio

Essenzialmente le attività in background erano libere di aggiornare i dati, aggiungere e rimuovere elementi, ecc. Una volta al secondo l'interfaccia utente esaminava i dati e si aggiornava. Poiché i dati non erano legati al thread, ciò non causava alcun problema con la sincronizzazione, ecc.

La soglia di aggiornamento dipende in realtà dalle aspettative degli utenti. Per noi, i nostri periodi di campionamento erano il nostro fattore limitante, quindi non c'era motivo di aggiornarlo più spesso di una volta al secondo. Altri potrebbero avere aggiornamenti più frequenti con un feedback più localizzato.

Questo era il modo più semplice e più prevedibile che potessimo trovare per mantenere sincronizzati i dati e l'interfaccia utente senza tutte le micro-attività che a volte si possono ottenere quando si utilizza Dispatcher.BeginInvoke e costrutti simili.

    
risposta data 12.09.2017 - 16:37
fonte
1

Hai ragione, questo è un po 'problematico, ecco perché è già stato risolto dal framework UI.

Di solito c'è un modo per inviare risultati parziali al thread dell'interfaccia utente. In Swing, ciò avviene tramite SwingWorker , in Android - AsyncTask . Quando questi vengono richiamati sul thread dell'interfaccia utente, tutti i risultati parziali precedenti vengono raccolti e elaborati dal thread dell'interfaccia utente contemporaneamente. Se c'è un modo per risparmiare tempo / spazio raccogliendo questi parziali invece di rifare il lavoro - questo è il momento di farlo. La cosa più semplice è prendere il risultato più recente e scartare tutti i precedenti (come in una barra di avanzamento), essenzialmente nel senso che il thread dell'interfaccia utente potrebbe vacillare, ma non ritardare.

Come viene gestita questa raccolta - controllando la presenza di un partial precedente durante l'invio di una nuova, o quando il thread dell'interfaccia utente è pronto per iniziare l'elaborazione non è qualcosa che dovresti armeggiare a meno che tu non sappia davvero che si tratta di un problema. Ma ovviamente puoi dare un'occhiata al codice della biblioteca e scoprire che cosa hanno pensato i vari designer della biblioteca.

    
risposta data 12.09.2017 - 16:55
fonte

Leggi altre domande sui tag