2009-11-16 7 views
16

Come ampiamente noto, qualsiasi cosa relativa ai componenti Swing deve essere eseguita su the event dispatch thread. Questo vale anche per models dietro i componenti, ad esempio TableModel. Abbastanza facile nei casi elementari, ma le cose diventano piuttosto complicate se il modello è una "live view" di qualcosa che deve essere eseguito su un thread separato perché sta cambiando rapidamente. Ad esempio, una visualizzazione live di una borsa valori su una tabella di distribuzione. I mercati azionari di solito non si verificano sull'EDT.Come sincronizzare il modello Swing con un modello "reale" che cambia rapidamente?

Quindi, qual è il modello preferibile per (de) accoppiare il modello Swing che deve essere sull'EDT e un modello "reale", thread-safe che deve essere aggiornabile da qualsiasi luogo, in qualsiasi momento? Una possibile soluzione sarebbe quella di fare in realtà split the model in due copie separate: il modello "reale" più la sua controparte Swing, che è un'istantanea del modello "reale". Vengono quindi (bidirezionalmente) sincronizzati sull'EDT di tanto in tanto. Ma questo sembra gonfiare. È davvero l'unico approccio praticabile o ci sono altri modi, o più standard? Biblioteche utili? Nulla?

+1

produttore/consumatore? –

risposta

11

posso consigliare il seguente approccio:

  • Luogo eventi che dovrebbero modificare la tabella in una coda "in attesa evento", e quando un evento viene inserito nella coda e la coda è vuota quindi richiamare il thread Event Dispatch per drenare la coda di tutti gli eventi e aggiornare il modello di tabella. Questa ottimizzazione significa non si è più invocare il filo spedizione evento per ogni evento ricevuto, che risolve il problema del filo manifestazione spedizione non tenere il passo con il flusso di eventi sottostante.
  • creazione Evitare di una nuova Runnable quando si richiama il filo evento invio utilizzando una classe interna senza stato per drenare la coda degli eventi in attesa all'interno del tuo pannello di implementazione tavolo.
  • Opzionale un'ulteriore ottimizzazione: Quando si scarica coda degli eventi in attesa di ridurre al minimo il numero di eventi di aggiornamento delle tabelle sparati da ricordare che devono righe della tabella di essere ridisegnata e poi sparare un singolo evento (o un evento per riga) dopo l'elaborazione di tutti gli eventi.

Esempio di codice

public class MyStockPanel extends JPanel { 
    private final BlockingQueue<StockEvent> stockEvents; 

    // Runnable invoked on event dispatch thread and responsible for applying any 
    // pending events to the table model. 
    private final Runnable processEventsRunnable = new Runnable() { 
    public void run() { 
     StockEvent evt; 

     while ((evt = stockEvents.poll() != null) { 
     // Update table model and fire table event. 
     // Could optimise here by firing a single table changed event 
     // when the queue is empty if processing a large #events. 
     } 
    } 
    } 

    // Called by thread other than event dispatch thread. Adds event to 
    // "pending" queue ready to be processed. 
    public void addStockEvent(StockEvent evt) { 
    stockEvents.add(evt); 

    // Optimisation 1: Only invoke EDT if the queue was previously empty before 
    // adding this event. If the size is 0 at this point then the EDT must have 
    // already been active and removed the event from the queue, and if the size 
    // is > 0 we know that the EDT must have already been invoked in a previous 
    // method call but not yet drained the queue (i.e. so no need to invoke it 
    // again). 
    if (stockEvents.size() == 1) { 
     // Optimisation 2: Do not create a new Runnable each time but use a stateless 
     // inner class to drain the queue and update the table model. 
     SwingUtilities.invokeLater(processEventsRunnable); 
    } 
    } 
} 
+0

Grazie, sembri esserci stato :-) –

+0

A proposito, quel codice ha un piccolo bug: se qualcuno aggiunge un StockEvent solo quando la coda sta per essere vuota, stockEvents.isEmpty() può restituire falso ma il processEventsRunnable termina prima che i nuovi eventi vengano aggiunti alla coda stockEvents. Quindi è bloccato. Una soluzione potrebbe essere quella di utilizzare un meccanismo diverso da isEmpty() per verificare se processEventsRunnable deve essere riavviato. –

+1

@ Joonas - Questo è davvero un buon punto. Penso che il problema sia risolto controllando that size() == 1 dopo aver aggiunto l'evento alla coda. Se la coda è vuota a questo punto, l'evento è già stato elaborato quindi non ci sono problemi, e se la dimensione è> 1, sappiamo che EDT deve essere già stato invocato da una chiamata di metodo precedente. – Adamski

2

Per quanto ho capito, non si desidera implementare le interfacce del modello Swing nel modello reale, vero? È possibile implementare un modello Swing come "vista" su una parte di un modello reale? Sarà tradurre il suo accesso in lettura getValueAt() per le chiamate del modello reale, e il modello reale notificherà modello swing circa i cambiamenti, sia fornendo un elenco di modifiche o assumendo quel modello swing si prenderà cura di quering i nuovi valori di tutto ciò che sta attualmente mostrando.

+0

A destra: non voglio implementare le interfacce Swing nel mio modello reale, perché so che il modello reale verrà aggiornato da altri thread che l'EDT, che violerebbe i requisiti di Swing. Penso che quello che stai descrivendo sia essenzialmente il "modello diviso". –

+0

Non è un grosso problema aggiornare il modello reale da altri thread, a condizione che l'interazione con Swing avvenga in EDT e il modello sia sicuro per i thread. Swing chiamerà metodi come getValueAT() in EDT, quindi l'unica cosa di cui preoccuparsi è l'invio di notifiche (fireSmthChanged) in EDT. Puoi farlo usando SwingUtilities.invokeLater – Dmitry

+0

... ma ovviamente ti troverai ad affrontare problemi delicati, come il numero sbagliato di bambini in un albero (perché qualcosa è cambiato da quando hai inviato una notifica a Swing), ecc. – Dmitry

2

L'approccio normale è inviare "segnali" di qualche tipo a cui l'interfaccia utente ascolta. Nel mio codice, utilizzo spesso un dispatcher centrale che invia segnali contenenti l'oggetto che è stato modificato, il nome del campo/proprietà più il vecchio e il nuovo valore. Nessun segnale viene inviato per il caso oldValue.equals(newValue) o oldValue.compareTo(newValue) == 0 (quest'ultimo per le date e BigDecimal).

Il thread dell'interfaccia utente registra quindi un listener per tutti i segnali. Quindi esamina l'oggetto e il nome e quindi lo traduce in una modifica nell'interfaccia utente che viene eseguita tramite asyncExec().

È possibile trasformarlo in un listener per oggetto e fare in modo che ogni elemento dell'interfaccia utente si registri automaticamente sul modello. Ma ho scoperto che questo diffonde il codice dappertutto. Quando ho un enorme insieme di oggetti su entrambi i lati, a volte uso solo diversi segnali (o eventi) per rendere le cose più gestibili.

+0

Ok, quindi hai un modello diviso, quindi invia notifiche di aggiornamento dal modello "reale" al modello dell'interfaccia utente. 'asyncExec()' sembra essere una funzione SWT. È essenzialmente simile a 'EventQueue.invokeLater()'? –

+0

Sì, usa 'EventQueue.invokeLater()' :) Il mio approccio è di mantenere le dipendenze tra i due modelli il più sottili possibile, così posso facilmente cambiarli entrambi. Finché i segnali non cambiano (molto), l'altro modello non sarà influenzato. –

Problemi correlati