View Full Version : [Java] Comunicazione tra thread
k_mishima
16-05-2011, 11:03
Salve a tutti
Ho scritto un programmino che parte creando una piccola GUI
Dietro un pulsante della GUI vi è un listener. Se premuto, si attiva il listener che crea un thread; in questo thread è eseguito il mio programma, è istanziata una certa classe e lanciato un suo metodo mooooooolto lungo.
Vorrei fare in modo che i 2 thread comunicassero e cioè che il secondo, durante l'esecuzione del metodo moooooolto lungo, mandi delle stringhe al primo, in modo che il primo possa visualizzarle in una JTextArea
Ad esempio invii: operazione 1 completata, inizio operazione 2, ..... completato....
Giusto per far capire all'utente che non è tutto bloccato ma ci vuole tempo e deve aspettare.
Come faccio a inviare queste stringhe dal secondo thread che esegue un codice in un file java separato dal primo, e a farle ricevere dal primo?
Grazie 1000
tylerdurden83
16-05-2011, 11:17
Potresti fare l'oggetto thread lavoratore un Observable, quello che lo starta un Observer, e registrare l'Observable, che manderà notifiche ai suoi Observer man mano che la computazione va avanti!
Il suggerimento di tylerdurden83 è valido.
Esiste poi una classe specifica per il tipo di situazione in cui ti trovi (un thread in background che vuole "parlare" alla gui), SwingWorker.
Anzichè usare un thread nudo e crudo usi uno SwingWorker che funziona come un thread in background e in più ha un paio di metodi pensati proprio per inviare notifiche in corso d'opera ad elementi dell'interfaccia grafica.
Supponiamo che adesso il tuo codice somigli a questo:
...actionPerformed(ActionEvent e) {
new Thread() {
public void run() {
...vai con l'operazione di lungo termine
}
}.start();
}
Diventerebbe:
...actionPerformed(ActionEvent e) {
new SwingWorker<Void, String>() {
protected void doInBackground() throws Exception {
...vai con l'operazione di lungo termine
}
protected void process(List<String> chunks) {
}
}.execute();
}
Il metodo process di SwingWorker è eseguito nel thread che gestisce la gui, il metodo doInBackground è eseguito da un thread diverso. Supponiamo che l'operazione in background sia divisibile in passaggi e che tu voglia notificare l'avanzamento di stato aggiungendo una stringa ad un'area di testo disponibile come campo della classe che dichiara l'actionPerformed (ma può trovarsi da qualsiasi altra parte). Diremmo:
...actionPerformed(ActionEvent e) {
new SwingWorker<Void, String>() {
protected void doInBackground() throws Exception {
publish("Partenza");
...vai con l'operazione in background
publish("Finito il passaggio 1");
...continua con l'operazione in background
publish("Finito il passaggio 2");
...continua
publish("Procedimento terminato");
}
protected void process(List<String> chunks) {
for(String message : chunks) {
jTextArea.append(message + "\n");
}
}
}.execute();
}
Cioè quel che "publish" nel metodo "doInBackground" è trasferito dal thread in background al thread EDT e passato al metodo process, all'interno del quale puoi gestirlo.
Pubblichi una stringa ma processi una lista perchè, per ragioni d'efficienza, lo SwingWorker compatta pubblicazioni multiple in una sola notifica (magari il processo in background impiega un nanosecondo per tre passaggi di stato, lo SwingWorker anzichè fare tre salti nell'EDT ne fa uno solo con una lista di messaggi).
k_mishima
16-05-2011, 20:57
Innanzitutto grazie per le risposte.
Al momento il codice dietro il mio bottone sarebbe questo
final popolamentoOntologia po = new popolamentoOntologia();
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
status.setText("Stato: Esecuzione...");
b.setEnabled(false);
p4.setEnabled(false);
Thread t = new Thread() {
public void run() {
try {
po.ontologyPopolation(getPathFashion(), getPathMondrian(), getPathOntology());
} catch (Exception e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
status.setText("Stato: Caricamento completato");
// b.setEnabled(true);
}
});
}
};
t.start();
}
});
Ho tutto il codice in un'unico metodo della classe popolamentoOntologia
Non posso dividerlo. Posso usare i publish nel metodo di popolamentoOntologia?
tylerdurden83
16-05-2011, 21:48
sorry ero al tel. allora vediamo un po, nei commenti ho aggiunto cosa non va bene
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent evt) {
// il metodo actionPerformed è eseguito nell'EDT
status.setText("Stato: Esecuzione...");
b.setEnabled(false);
p4.setEnabled(false);
// creare questo thread che fa il lavoro (ontologyPopolation) e startarlo qui dentro, ossia nell'EDT, NON è corretto. Devi usare un background thread come ti ha detto PGI-Bis
Thread t = new Thread() {
public void run() {
try {
po.ontologyPopolation(getPathFashion(), getPathMondrian(), getPathOntology());
} catch (Exception e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(new Runnable() {
public void run() {
status.setText("Stato: Caricamento completato");
// b.setEnabled(true);
}
});
}
};
t.start();
}
});
Io procederei così.
1) Partiamo dalla classe con il metodo che fa l'elaborazione tosta. Quella classe la dichiari in modo che extends Observable
2) Aggiungi un metodo tipo questo (nel mio caso è protected perchè sta in una superclasse):
/**
* Set this Observable's state as "changed" and notify its Observers.
*/
protected void changedState(int counter) {
setChanged();
notifyObservers(counter);
}
Poi:
3) Aggiungo al metodo che "elabora" questa istruzione:
// notify the Observers that another configuration has been tested
super.changedState(counter++);
Fin qui dovrebbe essere abbastanza easy. Nota che per ora non ci sono riferimenti a swing, abbiamo toccato solo il Core della tua applicazione, che contiene la classe che fa il lavoro duro. Questo metodo continua ad essere valido anche con altre gui etc. Passiamo alla parte swing:
4) La mia classe che gestisce il lancio della computazione è dichiarata così:
public class FireComputationTask extends Task<Player, Void> implements Observer, PropertyChangeListener {
Player è il tipo che doInBackground ritornerà, può essere anche void in caso. Void è per i risultati intermedi. Implementa Observer quindi può registrarsi per ricevere notifiche dagli Observable che ascolta, e PropertyChangeListener alla fine è un Observable più specifico di Swing diciamo.
5) Copia lo stato della gui in variabili d'istanza di questa classe, ad es:
private final String indirizzo;
public FireComputationTask(String indirizzo)
this.indirizzo=indirizzo
(indirizzo ad es potrebbe essere passato al costruttore facendo jtextbox.getText();
6) metodo fondamentale, doInBackground()
/**
* Executed in a background thread to find out the best Player obtainable from this AbstractGearOptimization instance.
* @return the best Player obtainable from this AbstractGearOptimization instance
*/
@Override protected Player doInBackground() {
// register this to call propertyChange when progress changes via setProgress
this.addPropertyChangeListener(this);
// add this to the list of observers of the AbstractGearOptimization
this.goa.addObserver(this);
// 100 / the number of combinations to test represents the percentage value of every single test
this.percentage = (float)100/this.combinations;
//Initialize progress property.
super.setProgress(0);
System.out.println("["+DateFormat.format(new Date())+"] Starting the optimization using "+this.goa.getClass());
// call the compute method to fire the actual optimization computation and retrieve the best Player for the passed GearOptimizationAlgorithm
return this.goa.compute();
}
questo è il mio. Nota come registro l'oggetto corrente come listener sia per ascoltare Observable che per ascoltare i cambiamenti della variabile progress (mediante Task.setProgress()).
7) In quanto Observer, implemento update
public void update(Observable o, Object arg) {
super.setProgress(Math.min((Integer)arg*this.percentage/100, 100));
}
8) e in quanto PropertyChangeListener
public void propertyChange(PropertyChangeEvent evt) {
if ("progress".equalsIgnoreCase(evt.getPropertyName())) {
int progress = (Integer) evt.getNewValue();
// set the progress bar to the new progress value
this.jprogressBar.setValue(progress);
}
}
9) per finire, il metodo succeeded chiude il tutto
@Override protected void succeeded(Player result) {
// dispose the progress bar frame
this.progressBarFrame.dispose();
...
}
Ricordati che:
- doInBackground è eseguito in un background thread. La chiamata al tuo metodo calcolatore DEVE essere fatta qui dentro, e non puoi accedere ai componenti gui
- succeed è eseguito nell'EDT una volta che doInBackground è terminato, quindi qui puoi accedere ai componenti gui, ma non eseguire task
Naturalmente anche la soluzione di PGI è corretta, la differenza sta principalmente nel fatto che con l'adozione di Observer-Observable hai notifiche sul progress anche fuori da Swing, quindi che ne so domani vuoi fare un utility per usarlo via command line il tuo programma, e già hai tutto pronto.
k_mishima
16-05-2011, 22:38
sembra complicato :D
provo e ti faccio sapere, ma ci vorrà un bel po mi sa, domani posto come è andata
tylerdurden83
16-05-2011, 22:43
Na, guarda queste due classi di esempio che ho appena scritto, prova anche a lanciarle:
import java.util.Observable;
public class Worker extends Observable {
public boolean doWork(){
for(int i=0; i<10; i++){
System.out.print("Working....");
try {
Thread.sleep((int)(5000 * Math.random()));
} catch (InterruptedException ex) {
return false;
}
this.changedState(i);
}
return true;
}
private void changedState(int counter) {
setChanged();
notifyObservers(counter);
}
}
import java.util.Observable;
import java.util.Observer;
public class Launcher implements Observer {
public void update(Observable o, Object arg) {
System.out.println(" blocchetto "+arg+" terminato.");
}
public static void main(String[] args){
Launcher launcher = new Launcher();
Worker worker = new Worker();
worker.addObserver(launcher);
worker.doWork();
}
}
Riesci a immaginarti il tuo PopolaOntologia tipo il mio doWork() diciamo?
k_mishima
16-05-2011, 22:47
si, la situazione è questa.
tylerdurden83
16-05-2011, 23:05
private class PopolaOntologiaTaskTask extends org.jdesktop.application.Task<Boolean, Void> implements Observer {
private final int contatore;
private final Worker worker;
private final JProgressBar progressBar;
private final JFrame frame = new JFrame();
PopolaOntologiaTaskTask(Application app, int contatore) {
// Runs on the EDT. Copy GUI state that
// doInBackground() depends on from parameters
// to PopolaOntologiaTaskTask fields, here.
super(app);
this.contatore=contatore;
this.worker=new Worker();
this.progressBar=new JProgressBar();
this.progressBar.setValue(0);
this.progressBar.setStringPainted(true);
this.progressBar.setMinimum(0);
this.progressBar.setMaximum(this.contatore);
this.frame.add(this.progressBar);
this.frame.setLocationRelativeTo(null);
this.frame.pack();
this.frame.setVisible(true);
}
@Override protected Boolean doInBackground() {
// Your Task's code here. This method runs
// on a background thread, so don't reference
// the Swing GUI from here.
this.worker.addObserver(this);
return this.worker.doWork(this.contatore);// return your result
}
@Override protected void succeeded(Boolean result) {
try {
this.frame.dispose();
// Runs on the EDT. Update the GUI based on
// the result computed by doInBackground().
if (this.get() == true) {
JOptionPane.showMessageDialog(null, "TUTTO OK", "CONGRATULAZIONI", JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(null, "KO", "DANNAZIONE", JOptionPane.ERROR_MESSAGE);
}
} catch (InterruptedException ex) {
} catch (ExecutionException ex) {
}
}
public void update(Observable o, Object arg) {
System.out.println("Setting the progress bar to "+Integer.parseInt(arg.toString()));
this.progressBar.setValue(Integer.parseInt(arg.toString()));
}
}
Sto usando il Task che sta nella libreria Swing di Netbeans, tuttavia scriverlo usando SwingWorker non richiede praticamente nessuna modifica se non togliere Application app dal costruttore.
tylerdurden83
16-05-2011, 23:15
Qui c'è un video per vedere cosa combina quel codice (http://www.youtube.com/watch?v=u6JBLD8jPoc), e qui ti ho messo il progetto con i sorgenti e tutto (http://rhadamanthys.homelinux.com:58318/PopolaOntologia.rar).
k_mishima
16-05-2011, 23:19
Non riesco a farlo, cambiando la definizione della classe mi si creano errori in seguito, purtroppo non sono un esperto e non riesco più a correggerli.
Il mio codice è abbastanza impasticciato mi sà.
Come IDE sto usando eclipse.
Ti passerei anche tutto il progetto per vedere se è fattibile la cosa ma non vorrei disturbarti troppo, è un po scocciante leggere il codice altrui, dimmi tu.
EDIT: Visto il video, molto elegante come soluzione :)
tylerdurden83
16-05-2011, 23:23
Ci sarebbe da smazzare un po tra progetti diversi per i due ide...l'ho convertito in normale swingworker, prova così;
private class PopolaOntologiaTaskTask extends SwingWorker<Boolean, Void> implements Observer {
private final int contatore;
private final Worker worker = new Worker();;
private final JProgressBar progressBar;
private final JFrame frame = new JFrame();
PopolaOntologiaTaskTask(int contatore) {
// Runs on the EDT. Copy GUI state that
// doInBackground() depends on from parameters
// to PopolaOntologiaTaskTask fields, here.
this.contatore=contatore;
this.progressBar=new JProgressBar();
this.progressBar.setValue(0);
this.progressBar.setStringPainted(true);
this.progressBar.setMinimum(0);
this.progressBar.setMaximum(this.contatore);
this.frame.add(this.progressBar);
this.frame.setLocationRelativeTo(null);
this.frame.pack();
this.frame.setVisible(true);
}
@Override public Boolean doInBackground() {
// Your Task's code here. This method runs
// on a background thread, so don't reference
// the Swing GUI from here.
this.worker.addObserver(this);
return this.worker.doWork(this.contatore);// return your result
}
@Override public void done() {
try {
this.frame.dispose();
// Runs on the EDT. Update the GUI based on
// the result computed by doInBackground().
if (this.get() == true) {
JOptionPane.showMessageDialog(null, "TUTTO OK", "CONGRATULAZIONI", JOptionPane.INFORMATION_MESSAGE);
} else {
JOptionPane.showMessageDialog(null, "KO", "DANNAZIONE", JOptionPane.ERROR_MESSAGE);
}
} catch (InterruptedException ex) {
} catch (ExecutionException ex) {
}
}
public void update(Observable o, Object arg) {
this.progressBar.setValue(Integer.parseInt(arg.toString()));
}
}
k_mishima
16-05-2011, 23:43
niente, mi segna errore sulle linee contenenti Worker sia nella zona globale sia nel metodo doInBrackground() e non posso provarlo.
Inoltre mi comporta un errore qui
JButton sfogliaFashion = new JButton("Sfoglia", new ImageIcon("image/iconAccess.jpg"));
sfogliaFashion.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
int returnVal = fileChooser.showOpenDialog(createGUI.this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
setPathFashion(file.getPath());
textFieldFashion.setText(file.getPath());
if ((getPathOntology() != null) && (getPathMondrian() != null)){
b.setEnabled(true);
}
}
}
});
p1.add(sfogliaFashion);
su questa riga
int returnVal = fileChooser.showOpenDialog(createGUI.this);
che non riesco a correggere (createGui è il nome della mia classe)
EDIT: Ma worker è la classe che fa il mio lavoro lungo?
EDIT2: Ok mi resta l'errore sul fileChooser per provare il tutto
EDIT3: Ho tolto gli errori ma la barra non si muove, smanetto ancora un po
tylerdurden83
17-05-2011, 00:14
niente, mi segna errore sulle linee contenenti Worker sia nella zona globale sia nel metodo doInBrackground() e non posso provarlo.
EDIT: Ma worker è la classe che fa il mio lavoro lungo?
EDIT2: Ok mi resta l'errore sul fileChooser per provare il tutto
EDIT3: Ho tolto gli errori ma la barra non si muove, smanetto ancora un po
1) Si Worker è una classe che ho fatto io ed è un simulatore della tua classe che fa il lavoro lungo (PopolaOntologia?). Worker, come la tua classe, estende Observable, e Worker ha una sleep che simula il lavoro computazionalmente pesante della tua classe. Sia nel caso di Worker che chiama sleep che della tua classe col metodo pesante, noterai che facendo come ti ho detto non si inchioda la gui. Se invece chiami Worker.doWork(), che esegue una sleep, dentro l'EDT, ti si inchioda tutto. Naturalmente la mia Worker non la devi usare, era solo un esempio di Observable che avevo postato prima di passare alla parte Swing.
3) Cerca di capire se il metodo update() del tuo Observer viene "sollecitato" quando l'Observable comunica che "è cambiato".
k_mishima
17-05-2011, 00:17
funziona :D
ora nel mio metodo per impostare % da 0 a 100% devo mettere 100 volte l'istruzione che aumenta di 1 il contatore, giusto?
changedState(conta++);
Comunque grazie infinite, sei stato gentilissimo :D
tylerdurden83
17-05-2011, 00:39
Dipende da quello che puoi fare. Ad esempio, nel codice che ti ho postato facevo:
public void update(Observable o, Object arg) {
this.progressBar.setValue(Integer.parseInt(arg.toString()));
}
Inoltre avevo settato
this.progressBar.setMinimum(0);
this.progressBar.setMaximum(this.contatore);
Immagina che contatore=3, quindi la progress bar può andare da 0 a 3. Immagina che update riceve come arg:
il numero 0 --> la progress bar resta a 0%
il numero 1 --> siccome la progress bar sa che può assumere valori da 0 a 3 è sufficientemente intelligente da settarti il valore del progress a 33%
il numero 2 --> segna 66%
il numero 3 --> segna 100%.
Questo è il caso facile. C'è un caso più complesso. Immagina che il tuo Worker.doWork() fa:
for(int i=0; i<100.000; i++){
// lavoro
this.changedState(i);
}
Se sai a priori che ti mandera esattamente 100.000 notifiche sei a cavallo, il metodo sopra funziona ancora perfettamente settando progressBar.setMaximum(100.000) perchè darà ad ogni incremento di un'unità il giusto peso (100 / 100.000). Tuttavia, se non sai a priori che il tuo lavoratore manderà 100.000 notifiche, e non puoi aspettare per costruire e visualizzare la progress bar perchè magari la usi per più di un task, come fare?
Setti la barra in modo che vada da 0 a 100.
this.jprogressBar = new JProgressBar(0, 100);
Se lasci:
public void update(Observable o, Object arg) {
this.progressBar.setValue(Integer.parseInt(arg.toString()));
}
inalterato però dopo il 100-imo update la barra è al 100% e te hai ancora 90.000 iterazioni, quindi fai:
// 100 / the number of combinations to test represents the percentage value of every single test
this.percentage = (float)100/this.combinations;
in pratica, 1 notifica non equivale a 1 punto di progresso, ma a this.percentage di progresso, e poi
setProgress((int)Math.min((Integer)arg*this.percentage/100, 100)
Così hai ad esempio che percentage=100/100.000=0,001, e quando arg è ad esempio 67.000, ti chiama
setProgress((int)Math.min((Integer)67.000*0.001/100, 100)
ossia
setProgress((int)Math.min(67, 100)
quindi ti setta la barra al 67%
Gin&&Tonic
17-05-2011, 10:53
postresti utilizzare gli exchanger.
k_mishima
17-05-2011, 11:11
Non mi è chiara una cosa, tu hai parlato di 100000 iterazioni e hai spiegato ottimamente cosa fare nel caso.
Io nel mio luuungo programma faccio sempre questo:
Query su un db su una o al max due tabelle in join
Per ogni risultato della query (while sul resultSet.next())
estraggo dati colonna x colonna e li inserisco in un individuo della mia ontologia (sto usando le api di protege, comunque se non le conosci non è importante ai fini della risposta)
Dunque i while hanno innanzitutto un numero di operazione dipendente dal numero dei record estratti dalle query, inoltre a causa dei diversi risultati delle query, all'interno avrò un numero di operazioni molto diverse tra loro in ogni record. (differenti numeri di colonne, richiesta di manipolazione dei dati, necessità di cercare elementi già inseriti precedentemente nell'ontologia, necessità di creare individui non trovati appartenenti a un while già processato....)
Quindi ad esempio un while potrebbe avere un'iterazione che ci mette 1ms, un altro un operazione che ci mette 0,01ms e così via.
Non conoscendo il numero totale di operazioni (calcolabile al limite a fine esecuzione sommando le varie iterazioni dei while con un contatore globale) e non essendo omogenei i tempi d'esecuzione dei cicli, non so come procedere.
Inoltre è possibile modificare leggermente il codice in modo che invii anche una stringa da usare per inserirla in una JTextArea?
Grazie:stordita:
tylerdurden83
17-05-2011, 11:57
Puoi costruirti una cosa del genere?
public class QueryManager{
private ResultSet resSet;
public int lookup(...){
...
this.resSet = ...
resultSet.last();
int rowCount = resultSet.getRow();
return rowCount;
}
public int lavora(){
for(Riga riga : this.resSet){
// lavoro
// notifico
}
}
}
Lo scopo è non avere la creazione del ResultSet mediante query E la lavorazione di ogni entry contenuta nel ResultSet in un solo metodo. Con il primo metodo ti fai le tue query per costruire un ResultSet e ti ritorni la sua dimensione, che puoi settare come max della ProgressBar ed eventualmente comunicare all'utente quanto è grosso, magari per fargli rifinire la query etc. A questo punto puoi chiamare il metodo che lavora, ovviamente sul ResultSet generato subito prima così non devi reinterrogare il db una seconda volta. Nel metodo lavora manderà anche le notifiche ovviamente.
non essendo omogenei i tempi d'esecuzione dei cicli, non so come procedere.
Qui la cosa si complica un po... se sono estremamente differenti forse la soluzione più facile è settare la progress bar in modalità indefinita. In realtà però tutte le progress bar non incrementano di 1% in maniera lineare. Ieri install di win 7 è stata il 60% del tempo totale per installarsi ferma sul valore del progress a 4%, quindi la non linearità non sarebbe uno scempio.
Inoltre è possibile modificare leggermente il codice in modo che invii anche una stringa da usare per inserirla in una JTextArea?
Questo è easy. Riprendiamo le classi di esempio di prima:
import java.util.Observable;
public class Worker extends Observable {
public void doWork(){
for(int i=0; i<10; i++){
System.out.print("Working....");
Thread.sleep((int)(5000 * Math.random()));
this.changedState("Messaggio:"+i);
}
}
private void changedState(String nuovoStato) {
setChanged();
notifyObservers(nuovoStato);
}
}
e
import java.util.Observable;
import java.util.Observer;
public class Launcher implements Observer {
public void update(Observable o, Object arg) {
// ho deciso che il formato delle notifiche inviatemi dall observable è "messaggio:id"
String[] messRicevuto = arg.toString().split(":");
System.out.println("Ho ricevuto il messaggio:"+messRicevuto[0]+" con id:"+messRicevuto[1]);
}
}
In pratica l'observable chiama notifyObservers(Object arg) passando un qualsiasi oggetto (anche tuo proprietario, con i suoi get() etc), che viene intercettato da update(Observer ob, Object arg).
k_mishima
17-05-2011, 17:21
grazie, ora la barra funziona bene, non è lineare in tutti i punti ma nella maggior parte si e mi va comunque benissimo.
più tardi provo a fare come hai suggerito riguardo il passaggio di messaggi, ora sono occupato su un mio bug, se vuoi dare un occhio lo trovi qui
http://www.hwupgrade.it/forum/showthread.php?p=35173676#post35173676
EDIT: Funziona benissimo anche il passaggio di messaggio, mi ha risolto l'ennesimo problema grazie.
vBulletin® v3.6.4, Copyright ©2000-2026, Jelsoft Enterprises Ltd.