invokeLater di EventQueue (e SwingUtilities) ti consente, in soldoni, di passare da un Thread diverso dall'EDT all'EDT. Ad esempio nel metodo main sappiamo di non essere nell'EDT, se vogliamo aprire una finestra dobbiamo passare a quel thread. Messa così sembra un po' magia, in realtà l'EDT è un comunissimo consumatore, ma è per capirsi.
SwingWorker si occupa, in un certo senso, del caso inverso: siamo nell'EDT e vogliamo far qualcosa in un thread diverso - per evitare di sovraccaricare l'EDT.
Meglio ancora, si occupa di una particolare classe di casi inversi: siamo nell'edt, vogliamo far fare qualcosa ad un altro thread E ciò che quel thread fa ha effetto su parti del sistema che devono essere gestire dall'EDT (solitamente qualche cambiamento in un componente AWT-Swing).
Di norma, infatti, se ci troviamo nell'EDT e vogliamo passare ad un altro thread basta Thread. In ipotesi:
Codice:
public void actionPerformed(ActionEvent e) { //EDT
new Thread() {
public void run() { //NON EDT
}
}.start();
}
In questo schema di solito l'esecuzione in background si coordina con l'EDT limitatamente ad un qualche effetto iniziale e finale, ad esempio premo il pulsante, apro una finestra di attesa, eseguo la computazione parallela, chiudo la finestra:
Codice:
public void actionPerformed(ActionEvent e) {//EDT
apriFinestraAttesa(); //qui siamo sempre nell'EDT
new Thread() {
public void run() { //NON EDT
eseguiComputazioneInBackground();
EventQueue.invokeLater(new Runnable() {
public void run() { //EDT
chiudiFinestraAttesa(); //qui siamo nell'EDT per via di invokeLater
}
});
}
}.start();
}
Tra indentazioni e graffe non è particolarmente bello a vedersi ma è relativamente "facile".
La trama si complica quando il thread in background vuole passare dei valori all'edt durante la computazione (per valori preformati che l'EDT voglia passare al thread in background esiste già una sincronizzazione tra l'azione che avvia un thread e la prima istruzione eseguita dal thread avviato).
Quando un Thread in esecuzione vuole passare un valore ad un altro Thread in esecuzione il primo Thread deve scrivere ciò che vuole comunicare sotto la vigilanza di un monitor e passare quel monitor insieme al valore al secondo Thread il quale, prima di tentare la lettura, si approprierà del monitor.
Sono operazioni assolutamente elementari ma piuttosto noiose. E qui arriva swing worker. SwingWorker somiglia ad un Thread solo che ha due metodi "run", uno principale che viene eseguito in concorso con l'EDT e uno secondario eseguito dall'EDT e attivabile a richiesta.
Il metodo principale è doInBackground, deputato ad eseguire la computazione parallela. Il metodo secondario è "process(List<T> valori)".
Il contenuto di process è eseguito dall'EDT. Process viene attivato dopo un'esecuzione di "publish(T)" - di solito in doInBackground. Il parametro T è il secondo nella lista dei parametri di SwingWorker:
new SwingWorker(..., T)
Ad esempio uno SwingWorker che, in background, genera dei punti, sarà:
Codice:
new SwingWorker<Void, Point>() {
}
Uno che produce interi sarà:
Codice:
new SwingWorker<Void, Integer>() {
}
eccetera.
Quando doInBackground genera un valore che vuole passare all'EDT lo fa tramite publish:
Codice:
new SwingWorker<Void, Point>() {
public Void doInBackground() { //fuori dall'EDT
Point p = new Point(10, 20);
publish(p);
return null;
}
}
L'invocazione di publish "va a finire" in process.
Codice:
new SwingWorker<Void, Point>() {
public Void doInBackground() { //fuori dall'EDT
Point p = new Point(10, 20);
publish(p);
return null;
}
public void process(java.util.List<Point> valori) {
Point p = valori.get(0);
}
}
La ragione per cui publish riceve un valore e process ne piglia una lista sta in ciò che invocazioni multiple di publish sono passibili di accorpamento per questioni di efficienza. Vale a dire che più invocazioni di publish nel doInBackground possono causare una sola invocazione di process con tutti gli argomenti nella stessa lista.
Codice:
new SwingWorker<Void, Point>() {
public Void doInBackground() { //fuori dall'EDT
publish(new Point(10, 20));
publish(new Point(30, 40));
publish(new Point(50, 60));
return null;
}
public void process(java.util.List<Point> valori) {//nell'EDT
//le tre invocazioni di publish in doInBackground possono
//causare tre invocazioni di process ma anche una sola invocazione
//con una lista di tre punti o due invocazioni. in ogni caso la faccenda
//si gestisce con un ciclo sulla lista
for(Point p : valori) {
fai qualcosa con p
}
}
}
Alla fine della fiera il punto centrale di SwingWorker sta nella garanzia di sincronizzazione tra publish e process: i valori che process riceve sono quelli che doInBackground ha creato prima di pubblicarli. Si tratta di un comportamento che le API non garantiscono invece per invokeLater:
Codice:
new Thread() {
public void run() {
final Point p = new Point();
p.setLocation(10, 20);
EventQueue.invokeLater(new Runnable() {
public void run() {
p.x ? //può valere o zero o dieci
}
});
}.start();
Codice:
new SwingWorker<Void, Point>() {
public Void doInBackground() {
Point p = new Point();
p.setLocation(10, 20);
publish(p);
return null;
}
public void process(List<Point> valori) {
Point p = valori.get(0);
p.x = ? //sicuramente 10
}
}.execute();
A margine, l'omologo di SwingWorker con Thread e invokeLater sarebbe:
Codice:
new Thread() {
public void run() {
final Point p = new Point();
synchronized(p) {
p.setLocation(10, 20);
}
EventQueue.invokeLater(new Runnable() {
public void run() {
synchronized(p) {
p.x = ?; //deve valere 10
}
}
});
}
}.start();
Tutto qua, nulla di trascendentale.