View Full Version : [JAVA] Uscire da un ciclo While/For con la pressione di un bottone
Salve a tutti,
è da poco che programmo in linguaggio Java, uso l'IDE NetBean 6.8 ...
Sto creando un programmino che mi calcoli i tempi di carica di un tipo di batteria e tramite un ciclo while effettuo un conto alla rovescia per quanto riguarda il timer.
Uso un bottone START per avviare il ciclo; ma mi sono accorto che se voglio bloccare il timer o comunque chiudere il programma devo forzare la chiusura di tutto il programma stesso. All'interno del while ho provato anche a mettere un if che mi controllasse in caso di bottone STOP premuto il cambio di una variabile booleana. Ma nulla! mi sono accorto che neanche me lo fa premere, non è in ascolto.
public void actionPerformed(ActionEvent e) {
String bottone = e.getActionCommand();
if (bottone.equals("Avvia Timer")){
int time = Integer.parseInt(timer.getText());
carica.setText("BATTERIA IN CARICA...");
String i1 = new String();
boolean flag=true;
int i=time;
while (flag && i>0){
if (bottone.equals("Ferma Timer")){
flag=false;}
i1 = Integer.toString(i);
System.out.println(i1);
i--;
timer.setText("");
timer.append(i1);
// attesa 1 secondo-----
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
Logger.getLogger(finestra.class.getName()).log(Level.SEVERE, null, ex);
}
// ----------------------
}
carica.setText("CARICA COMPLETATA !!!");
timer.setText(" BATTERIA CARICA ");
}
}
Come fare dunque??? Cercando online ho visto che bisogna usare i Thread, ma non li ho ben capiti ...
Attendo vostre risposte, per favore e grazie !
banryu79
24-03-2010, 12:20
il metodo actionPerformed viene eseguito dall'Event Dispatching Thread di AWT/Swing.
Codesto thread in pratica si occupa di prevelevare gli eventi che trova nella coda degli eventi di sistema di AWT/Swing e di processarli.
Se tu dentro quel metodo (actionPerformed) quindi lanci un ciclo o comunque esegui delle computazioni che richiedono un certo tempo per essere portate a termine, per tutto quel tempo costringi l'EDT a eseguire il tuo codice e a non essere libero di tornare a prelevare eventi nella coda di sistema.
Il che implica che se l'utente, nel mentre, tenta di premere un bottone o comunque di interagire con l'interfaccia grafica, si ritrova "in mutande" con un 'interfaccia che sembra non cagarlo di striscio, il che è quantomeno frustrante (per l'utente s'intende, l'interfaccia se ne frega altamente di queste piccole questioni umane :D).
La soluzione? Ovviamente tenere meno impegnato possibile il buon EDT e lasciarlo libero di tornare alle sue faccende.
Nel tuo caso osservo che nell'actionPerformed distingui due sorgenti diverse di eventi:
- quella identificata dall'actionCommand "Avvia Timer";
- quell'altra identificata dall'actionCommand "Ferma Timer".
Noto anche altri due elementi: un flag di controllo e un ciclo che viene fermato dal menzionato flag (oltre che da un'altra condizione legata all'esecuzione del ciclo stesso).
Ebbene, potresti risolvere l'enpasse così:
* il flag di controllo lo dichiari fuori dal metodo actionPerformed, allo "scope superiore";
* in actionPerformed gestisci l'evento scatenato da "Avvia Timer" 1) resettando il flag di controllo esterno del ciclo e 2) lanciando un altro thread che esegue le tue operazioni cicliche e che legge periodicamente il flag di controllo esterno
* in actionPerformed gestisci l'evento scatenato da "Ferma Timer" settando il falg di controllo per terminare il ciclo di operazioni eseguite dal tuo thread esterno.
Un modo di rappresentare le operazioni cicliche da eseguire nel thread esterno potrebbe essere quello di codificarle come implementazione di un Runnable.
A questo punto però sorgono almeno altre due questioni:
1) la neccessità di gestire in modo corretto il flag esterno visto che di fatto diventa una risorsa condivisa da 2 thread (l'EDT ci scrive sopra, l'altro thread ci legge. Dato che il flag è una variabile di tipo boolean, non occorre dichiararla come volatile, le letture e scritture su di essa sono atomiche).
2) la neccessità di gestire il caso in cui si verificasse l'evento "Avvia Timer" una seconda volta, mentre è già in esecuzione il tuo thread esterno, perchè l'utente ha cliccato nuovamente sul relativo bottone.
Qui si potrebbe decidere di disabilitare il bottone durante la gestione dell'evento "Avvia Timer", per poi riabilitarlo durante la gestione dell'evento "Ferma Timer". Un'altra opzione è data dall'ultilizzo di un'altro flag specifico.
In pseudo-codice:
// flag condiviso tra i thread
boolean flag;
...
// metodo eseguito dall'EDT
actionPerformed(ActionEvent e) {
if (e.actionCommand equals "Avvia Timer") {
resetto flag;
disabilito bottone "Avvia timer";
creo nuovo thread con istanza di MyTask;
lancio il thread;
}
if (e.actionCommand equals "Ferma Timer") {
setto il flag;
riabilito bottone "Avvia timer";
}
}
...
class MyTask implements Runnable {
run() {
// operazioni fatte eseguire su un altro thread:
implementazione del ciclo di operazioni
// uso del flag per il controllo del ciclo
}
}
Questa è solo un'idea, e nemmeno "pulita" al 100%.
Un'alternativa è data dall'uso della classe SwingWorker.
Ti linko questa breve guida a Swin (http://www.hwupgrade.it/forum/showthread.php?t=2005654)g, che contiene degli utili chiarimenti alle questioni che stai affrontando (il discorso dell'EDT in particolare, all'inizio; alle ultime pagine trovi un'introduzione all'uso di SwingWorker).
Per le questioni sull'uso del multithreading in Java, ti consiglio un buon manuale o comunque una lettura introduttiva dei relativi capitoli nei Java Tutorials che trovi online.
il metodo actionPerformed viene chiamato ed eseguito dentro il thread AWT, i cui compiti sono tra l'altro il repaint dei componenti (ecco perchè non riesci a cliccare sul bottone) e molte altre cose (che non sto qui ad elencare), la cosa che tu fai è eseguire un ciclo che in pratica congela le attività del thread si AWT fino a quando non hai finito la ricarica della finta batteria...
per fare quello che vuoi ti serve un Thread, visto che stai iniziando li hai già studiati?
perchè se no puoi utilizzare la classe javax.swing.Timer (http://java.sun.com/docs/books/tutorial/uiswing/misc/timer.html) oppure se il codice lo vuoi eseguire nel metodo actionPerformed usi uno SwingWorker (http://java.sun.com/docs/books/tutorial/uiswing/concurrency/worker.html) che non ti obbligano a crearti un tuo Thread :)
mhmm vi ringrazio molto!!!
Mi metto un pò a studiare la cosa e poi vi aggiorno su quello ke ho fatto... ( speriamo)
a presto!
mhmm vi ringrazio molto!!!
Mi metto un pò a studiare la cosa e poi vi aggiorno su quello ke ho fatto... ( speriamo)
a presto!
La cosa sembra più complicata del del dovuto ed io non sono proprio esperto...:cry:
in aiutino in più? o posso rinunciare finchè non conoscerò bene java!?
banryu79
24-03-2010, 17:31
La cosa sembra più complicata del del dovuto ed io non sono proprio esperto...:cry:
in aiutino in più? o posso rinunciare finchè non conoscerò bene java!?
Rinunciare... mai! :D
Procedere in modo razionale nell'acquisizione delle competenze invece, è sicuramente una strada percorribile. Magari per il momento solo per quelle strettamente neccessarie per superare questo specifico problema, il che implica capire la natura di alcune situazioni che si sono presentate e investire il tempo neccessario (pochi giorni) per digerirle una prima volta e produrre la soluzione al problema.
Prima di tutto, dato che usi Swing e il problema si manifesta a causa della natura single-threaded di Swing/AWT, dovresti capire questo aspetto.
Trovi una prima spiegazione nelle primissime pagine del tutorial che ti ho linkato prima (investendo 3 orette, te lo puoi comodamente leggere tutto: se usi Swing non sarà affatto tempo sprecato).
Quindi, avendo compreso questo, e la conseguente neccessità di dover implementare una parte della tua funzionalità con un altro thread, potrebbe essere utile leggere qualcosa del supporto che Java in quanto linguaggio offre alla programmazione multithreading, tanto per conoscere le basi-basi (classi Thread, Runnable, keyword syncrhonized, metodi wait() e notify di Object, metodi statici della classe Thread) magari reperendo qualche tutorial online (con Google trovi di tutto, io ti indico l'indice dei Java Tutorial (http://java.sun.com/docs/books/tutorial/reallybigindex.html)).
Quindi affronti il tuo problema: analizzi ciò che ti si para davanti per capire le cose e trovare una soluzione da implementare.
Ho cercato di fare proprio questo nel mio post precedente; speravo che le osservazioni unite al pseudo-codice postato potessero esserti utili per darti una traccia, un'idea, farti riflettere.
Considera che una volta che hai capito bene una cosa, anche se ti richidede del tempo, poi quella cosa, quei concetti, li ritrovi in diverse salse in vagonate di altri casi, e tu hai dalla tua una base di conoscenza in più utile a fronteggiarli senza tanti patemi, dunque io ti consiglio di investire del tempo nello studio.
Poi se hai domande specifiche spara: noi qui (io nel mio piccolo) ti si aiuta, se si può.
TI ringrazio tantissimo!!! è che di mio sono molto frettoloso e pensavo che creare un Timer fosse cosa semplice ( ovviamente conoscendo il sistema diventa semplice ) o che comunque nn richiedeva particolari conoscende di Java. Mi metterò sotto, quando ho tempo, nel mio viaggio alla conoscenza di questo fantastico Linguaggio.
E' stato un piacere alla prossima! Perchè di prossime ce ne saranno ! Ahahaha!
Saluti
banryu79
25-03-2010, 10:28
TI ringrazio tantissimo!!! è che di mio sono molto frettoloso e pensavo che creare un Timer fosse cosa semplice (ovviamente conoscendo il sistema diventa semplice) o che comunque nn richiedeva particolari conoscende di Java.
Se il tuo scopo è puramente "ludico" o di esercizio, proviamo a ragionarci assieme. Tieni presente però, che una classe Timer esiste già nel package java.swing: puoi, quando crei una sua istanza, impostare un intervallo di tempo a piacere e passargli un ActionListener; quando poi lo avvii, il timer emetterà un ActionEvent ogni volta che sarà passato l'intervallo di tempo specificato. Questo timer, java.swing.timer, è pensato per essere usato appunto per gestire azioni che coinvolgono l'interfaccia grafica (perchè il timer emette un ActionEvent che viene ascoltato dagli ActionListener registrati sul timer, i quali vedono le loro azioni, definite nel metodo actionPerformed, venire eseguite dall'Event Disptach Thread.
Puoi leggere un piccolo tutorial a proposito qui (http://www.hwupgrade.it/forum/newreply.php?do=newreply&p=31376021).
E qui (http://java.sun.com/javase/6/docs/api/javax/swing/Timer.html) ci sono i javadoc di questa classe.
Poi esiste anche un'altra classe "timer", nel JDK, ed è java.util.Timer.
Questa classe è "più generica" della precedente, e soddisfa quindi uno spettro più ampio di necessità, ma presenta anch'essa dei limiti.
Qui (http://java.sun.com/javase/6/docs/api/java/util/Timer.html) trovi i javadoc.
E qui un articolo (http://java.sun.com/products/jfc/tsc/articles/timer/) che tratta di entrambe.
Se invece il tuo scopo, come dicevo prima, è quello i esercitarti, creare un "timer" utilizzando un altro thread può essere fatto in molti modi diversi.
Prendendo spunto da quanto hai scritto nel primo post, diciamo che la neccessità in oggetto è quella di definire una classe personalizzata che rappresenta un "countdown".
Lo scopo del nostro "countdown" è quello di contare alla rovescia la quantità di "tempo" che gli impostiamo.
Deve fare questo in maniera "non bloccante", ovvero il thread in cui viene avviato il countdwon non deve eseguire le operazioni del countdown stesso, altrimenti resterebbe "bloccato" in questa attività invece di procedere ad eseguire le operazioni successive. Per ottenere questo il countdown dovrà essere implementato in modo da usare internamente un suo thread, e, sempre per questo motivo, quando ha finito deve inviare un segnale a tutti coloro che sono interessati (detti ascoltatori) ad ascoltare questo evento (countdown terminato).
Quindi il countdown deve avere al suo interno una lista di ascoltatori; deve avere un metodo per registrare gli ascoltatori interessati, per rimuoverli e per notificarli e tutto questo implica anche il fatto che dobbiamo definire una interfaccia che rappresenti un ascoltatore (chiamiamola senza tanta fantasia CountdownListener).
Cominciamo implementando questa base; in futuro potremmo aggiungere altre funzionalità, per esempio fare in modo che il "countdown" mandi un segnale a tutti gli ascoltatori registrati ogni tot di tempo, fino a che non termina.
Ricapitoliamo:
classe Countdown
[segnala il passaggio di un intervallo di tempo, impostabile, contando a partire da quando il countdown stesso viene avviato]
metodi
- start [avvia il countdown]
- addListener(CountdownListener l) [registra un ascoltatore]
- removeAllListener [de-registra tutti gli ascoltatori registrati]
- notifyListeners [notifica a tutti gli ascoltatori che il countdown è terminato]
e
interfaccia CountdownListener
[definisce un ascoltatore interessato all'evento emesso al termine del countdown su cui l'ascoltatore stesso viene registrato]
metodi
- countdownEnded [invocato al termine del countdown]
Qui sotto, a mo di esempio, trovi una implementazione babbana di ciò che ho appena definito...
banryu79
25-03-2010, 10:37
package countdown;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
/**
* Un conto alla rovescia, non bloccante, che notifica tutti gli ascoltatori registrati al passaggio del
* prefissato intervallo di tempo, dall'avvio del countdown.
*
* @author Francesco
*/
public class Countdown
{
final private long DELAY;
final private Runnable COUNTER_THREAD;
// listerns support for countdown events
private Collection<CountdownListener> listeners;
/**
* Construct a countdown that count for delay milliseconds
* @param delay the delay, in millisec
*/
public Countdown(long delay)
{
DELAY = delay;
COUNTER_THREAD = new Counter();
}
/**
* Start the countdown (non-blocking)
*/
public void start()
{
Thread t = new Thread(COUNTER_THREAD);
t.start();
}
/**
* Register l for countdown events.
* @param l the COuntdownListener to be registered.
*/
public void addListener(CountdownListener l)
{
// the listener must not be null
if (l == null) {
return;
}
// init listeners
if (listeners == null) {
listeners = new HashSet<CountdownListener>(10);
}
listeners.add(l);
}
/**
* Deregister all listeners
*/
public void removeAllListeners()
{
Iterator<?> it = listeners.iterator();
while (it.hasNext()) it.remove();
}
protected void notifyListeners(float seconds)
{
Iterator<CountdownListener> it = listeners.iterator();
while (it.hasNext()) {
CountdownListener l = it.next();
l.countdownEnded(seconds);
}
}
/**
* Simple countdown implementation, wait for DELAY millisec.
* If the countdown thread is interrupted immediatly end the countdown and notify all listeners, for
* semplicity.
* Also notify the listeners about the elapsed time.
*/
class Counter implements Runnable
{
public void run() {
long started = System.nanoTime();
try {
Thread.sleep(DELAY);
}
catch(InterruptedException ignored) {
// do nothing
}
finally {
float elapsed = (System.nanoTime()-started)/1000000000f;
notifyListeners(elapsed);
}
}
}
}
Il countdown viene inizializzato con un valore in millisecondi impostabile a piacere.
Quando viene fatto partire, internamente crea un nuovo Thread a cui passa un Runnable implementato dalla classe interna Counter, che appunto attende (mette a dormire il thread per il tempo specificato) che sia trascorso il lasso di tempo impostato per poi notificare il termine del countdown stesso e il tempo trascorso a tutti gli ascoltatori registrati.
banryu79
25-03-2010, 10:44
package countdown;
/**
*
* @author Francesco
*/
public interface CountdownListener
{
/**
* Invoked when the countdown ended.
* @param seconds the time elapsed from countdown start, in seconds.
*/
public void countdownEnded(float seconds);
}
Questa interfaccia definisce un ascoltatore che vuole essere notificato del termine di countdown specifico.
Quando il countdown su cui viene registrato termina, verrà invocato il metodo 'countdownEnded'.
banryu79
25-03-2010, 10:53
Usiamo il nostro countdown:
package countdown;
/**
*
* @author Francesco
*/
public class ConsolleCountdown
{
public static void main(String... args)
{
// creation and listener registration:
Countdown c1 = new Countdown(1000 * 5);
c1.addListener(new CountdownListener() {
public void countdownEnded(float seconds) {
System.out.println("Countdown ended after "+seconds+" sec.");
}
});
System.out.println("Countdown created.");
// execution:
c1.start();
System.out.println("Countdown started!");
System.out.println("I can do other things while c1 is counting.");
System.out.println("For example, I can sleep for 1 sec.");
sleep(1000);
System.out.println("And again...");
sleep(1000);
System.out.println("...and again!");
sleep(1000);
System.out.println("Preatty boring, uh?");
}
private static void sleep(long millis) {
try {
Thread.sleep(millis);
}
catch(InterruptedException ignored) {
// gnam... good taste!
}
}
}
Il thread che esegue il main, nel momento in cui invoca c1.start() non esegue anche il codice del countdown, crea semplicemente un altro thread che fa questo lavoro e lo avvia:
...nella classe Countdown
/**
* Start the countdown (non-blocking)
*/
public void start()
{
Thread t = new Thread(COUNTER_THREAD);
t.start();
}
Il thread che esegue il main procede quindi con le operazioni successive, stampando vari messaggi sullo standard output e mettendosi in sleep diverse volte, mentre nel frattempo l'altro thread sta eseguendo il countdown.
Da notare quindi, che il thread che chiama il metodo dell'ascoltatore countdownEnded() e stampa sullo standard output l'ultimo messaggio ("Countdown ended after XYZ sec.") non è il "thread del main" ma è il "thread del countdown".
banryu79
25-03-2010, 11:17
Proviamo a vedere come utilizzare Countdown nel tuo caso, con Swing:
public void actionPerformed(ActionEvent e) {
String bottone = e.getActionCommand();
if (bottone.equals("Avvia Timer")) {
long time = Long.parseLong(timer.getText());
Countdown counter = new Countdown(time);
counter.addListener(new CountdownListener(){
public void countdownEnded(float seconds) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
carica.setText("CARICA COMPLETATA !!!");
}
});
}
});
carica.setText("BATTERIA IN CARICA...");
counter.start();
}
if (bottone.equals("Ferma Timer")) {
//carica.setText("CARICA COMPLETATA !!!");
JOptionPane.showMessageDialog(null, "Funzionalita' non ancora supportata");
}
}
Come vedi, il codice da eseguire nel caso venga premuto il bottone "Avvia Timer" è brutto forte...
Dato che il thread che chiamerà il metodo countdownEnded come abbiamo visto è il thread creato da Countdown e non l'EDT, normalmente non potrei eseguire chiamate a metodi di componenti grafici dell'interfaccia utente (perchè appunto DEVE essere l'EDT a eseguire queste operazioni) e dovrei usare l'escamotage che vedi nel codice qui sopra (incapsulare le operazioni che devono essere fatte eseguire dall'EDT in un Runnable, e passare quest'ultimo al metodo invokeLater della classe SwingUtilities che si preoccupa di inserire il Runnable nella coda degli eventi di Swing).
Fortunatamente ci sono alcune (pochissime) operazioni che possono essere invocate anche da altri thread; il metodo setText di tutti i JTextComponent è una di queste (è un metodo cosidetto thread-safe).
Quindi il codice sopra diventa questo:
public void actionPerformed(ActionEvent e) {
String bottone = e.getActionCommand();
if (bottone.equals("Avvia Timer")) {
long time = Long.parseLong(timer.getText());
Countdown counter = new Countdown(time);
counter.addListener(new CountdownListener(){
public void countdownEnded(float seconds) {
carica.setText("CARICA COMPLETATA !!!");
}
});
carica.setText("BATTERIA IN CARICA...");
counter.start();
}
if (bottone.equals("Ferma Timer")) {
//carica.setText("CARICA COMPLETATA !!!");
JOptionPane.showMessageDialog(null, "Funzionalita' non ancora supportata");
}
}
Che è già più accettabile.
OMG! :eek:
Noto con piacere e paura che ho parecchia strada da fare ancora!
Ho letto ed importato nel mio progetto, che sto odiando :D , tutto il materiale esaustivo e dettagliato che mi hai fornito.
Spiego quello che ho capito, hai implementato :
- la classe Countdown, al cui interno vi sono dei metodi che gestiscono rispettivamente : start , creazione dei Listener, rimozione dei Listener e l'evento di notifica;
- la classe Counter per quanto riguarda il Runnable, che penso effettua appunto il conteggio alla rovescia del tempo in millisecondi che gli viene passato dalla variabile DELAY;
In pratica il ciclo dovrebbe essere questo :
- metto i dati, (tempo in millisecondi nella famosa JTextArea "timer")
- Schiaccio il pulsante Avvia Timer,
- crea una variabile di tipo long e vi mette dentro il tempo presente in "timer",
- crea una variabile di tipo Countdown e gli passa il dato contenuto nella variabile di tipo long time, dove all interno del metodo la imposta a DELAY,
- crea il listener ovvero quello su cui accadrà l'evento cioè il "countdownEnded" che viene invocato a fine countdown e che cambia la scritta della Jlabel in CARICA COMPLETATA !!! ( ovviamente questo succede solo quando il countdown è finito, perchè è lì che viene invocato il "countdownEnded" )
- mette la scritta della JLabel in BATTERIA IN CARICA... ,
- fa partire il conteggio. ( ance se non ho ben capito come )
Spero che sia, non dico giusto, ma si avvicina a quello che fa.
Ho notato che hai messo il messaggio "Funzionalita' non ancora supportata" per quanto riguarda il tasto Ferma Timer, e se volessi fermare il countdown ?
non si potrebbe implementare un altro metodo come quello dello start() che blocchi il countdown ???
Mi rendo conto che sono una palla al piede ! :doh:
GRAZIE VERAMENTE PER IL TEMPO CHE TI STO FACENDO PERDERE !
banryu79
26-03-2010, 09:14
OMG! :eek:
Noto con piacere e paura che ho parecchia strada da fare ancora!
Ti assicuro che è, appunto, un piacere e che a proposito di strada da fare siamo in due :) non farti impressionare.
Spiego quello che ho capito, hai implementato :
...
Spero che sia, non dico giusto, ma si avvicina a quello che fa.
Hai capito tutto.
Ho notato che hai messo il messaggio "Funzionalita' non ancora supportata" per quanto riguarda il tasto Ferma Timer, e se volessi fermare il countdown ?
non si potrebbe implementare un altro metodo come quello dello start() che blocchi il countdown ???
Beh, avrai notato che nella classe Countdown l'operazione di contare il tempo da far trascorrere è implementata nel metodo run della classe Counter, che implementa l'interfaccia Runnable (il metodo run è appunto dichiarato in questa interfaccia, ed è il metodo che viene chiamato quando viene invocato il metodo start del Thread costruito con un Counter).
Una soluzione che richieda modifiche minimali all'implementazione di sopra e aggiunga il supporto all'interruzione del countdown potrebbe essere realizzata sfruttando il meccanismo di "interrupt" della classe Thread (http://java.sun.com/javase/6/docs/api/).
Per capire la cosa dovresti prima conoscere questo meccanismo.
Per brevità ti dico solo che la classe Thread ha al suo interno un flag che viene settato quando un thread riceve un interrupt. Quando poi l'interrupt viene gestito, il flag viene resettato.
Invocare il metodo interrupt() su un'istanza di Thread, significa appunto segnalare a quell'istanza una "richiesta" di interruzione. Dico richiesta, perchè il thread in questione deve supportare in qualche modo il fatto di poter essere interrotto, altrimenti ciccia. Cioè, nel flusso di esecuzione che il thread stesso esegue, deve esistere la gestione di un'eventuale interruzione.
Dato che il flusso di esecuzione di un thread inizia con il codice contenuto nel metodo run di quel Thread, è nel corpo del metodo run che, in punti/momenti opportuni vanno gestite le interruzioni.
Nel prossimo post ti faccio l'esempio di come supportare l'interruzione di un thread che esegue un semplice ciclo infinito, in due modi diversi.
Poi ti mostro come supportare l'interruzione nel nostro thread Countdown.
Posto a rate perchè oggi a lavoro ho una giornata un pelo più vispa di ieri (che invece era calma piatta).
Mi rendo conto che sono una palla al piede ! :doh:
Non vedo perchè, non sono mica costretto a risponderti ne ad aiutarti, lo faccio perchè mi piace.
Buongiorno,
spero che la giornata sia iniziata abbastanza bene. Continuo a ringraziarti e non mi stancherò mai di farlo, per il piacere, al quanto pare di entrambi, ed al tempo impegnato per aiutarmi a comprendere questo meraviglioso linguaggio.
Sono felice di aver capito quello che hai fatto prima, il che vuol dire che non sono tanto bloccato di cervello ehehehehe !
Proviamo ad andare avanti :D
Vediamo se ho capito da quello che ho letto : bisogna implementare un altro metodo nella classe Countdown, simile a quella dello start, però per l' interrupt() e per fare questo bisogna :
- aggiungere un altra variabile nella classe Countdown oltre a queste :
public class Countdown
{
final private long DELAY;
final private Runnable COUNTER_THREAD;
// listerns support for countdown events
private Collection<CountdownListener> listeners;
tipo : final private boolean FLAG;
e nel metodo Countdown :
public Countdown(long delay)
{
DELAY = delay;
COUNTER_THREAD = new Counter();
}
modificarlo così :
public Countdown(long delay,boolean flag)
{
DELAY = delay;
COUNTER_THREAD = new Counter();
FLAG = flag;
}
ovviamente modificando anche la chiamata al metodo quando schiacchio il bottone start.
poi implementare il metodo interrupt() così :
public void interrurpt()
{
if ( FLAG){
t.interrupt(); // il thread è stato creato prima in start()
}
}
-----------------------------------------------------------
Andiamo alla gestione dei bottoni, ovvero nell'actionPerformed:
-inizializzo una variabile esterna al controllo se i singoli tasti sono attivati;
boolean flag;
- le operazioni da svolgere quando viene premuto il bottone "Avvia Timer" , che sono :
if (bottone.equals("Avvia Timer")) {
long time = Long.parseLong(timer.getText());
Countdown counter = new Countdown(time);
counter.addListener(new CountdownListener(){
public void countdownEnded(float seconds) {
lab2.setText("CARICA COMPLETATA !!!");
diventano così : ( perchè ho aggiunto il passaggio della variabile flag)
if (bottone.equals("Avvia Timer")) {
flag=false;
/* non so se è giusto inizializzarla a false, tanto non dovrebbe fare nulla, è solo un modo per inizializzarla e comunque differirla dal true che servirà nella gestione dell'interrupt() quando viene schiacciato il pulsante "Ferma Timer" */
long time = Long.parseLong(timer.getText());
Countdown counter = new Countdown(time,flag); //<----- MODIFCA
counter.addListener(new CountdownListener(){
public void countdownEnded(float seconds) {
lab2.setText("CARICA COMPLETATA !!!");
- le operazione da svolgere quando si preme "Ferma Timer", saranno :
if (bottone.equals("Ferma Timer")) {
flag=true;
// e adesso !?!??!?!?!
}
Qui mi sono impanicato, sperando che il resto sia giusto! :D
Ti auguro buona giornata!
banryu79
26-03-2010, 10:48
Primo passo: definimo a mo di esempio un thread "ciuccia CPU". Un thread "ciuccia CPU" è un thread che gira in loop continuo senza fare nulla di utile; in pratica una pura ciofeca, eccolo qua:
public class CiofecaTread extends Thread
{
@Override
public void run()
{
System.out.println("<Thread>loop started.");
while (true) {
// magia: ciuccia la CPU facendo assolutamente niente
// tranne girare come un criceto impazzito nella sua ruota.
}
// istruzione irraggiungibile:
// System.out.println("<Thread>loop ended.");
}
public static void main(String... args) {
CiofecaTread t = new CiofecaTread();
t.start();
System.out.println("<Main>thread launched!");
System.out.println("<Main>sleeping...");
}
}
Un thread del genere non supporta le interruzioni: nel suo metodo run non c'è traccia di questo supporto, dato che:
1) non si controlla mai se il flag di interrupt del thread è stato settato (cosa che accadrebbe se nel main come ultima istruzione aggiungessi una chiamata al metodo interrupt() del thread) per gestire l'eventuale interruzione;
2) non si fa uso di nessun metodo della classe Thread che lanci l'eccezione InterruptedException (come ad esempio Thread.sleep) e costringa a gestirla in un blocco try-catch dove è possibile supportare l'interruzione;
3) non si fa neppure uso di nessuna varibile di 'flag' per controllare la condizione del ciclo, e avere così la possibilità di terminarlo.
Proviamo ora a supportare l'interruzione con la tecnica descritta al punto (3).
Usiamo un semplice boolean come flag, che faccia da condizione per il ciclo.
Il metodo run di Thread è stato sovvrascritto nella nostra classe CiofecaTread per eseguire il ciclo "infinito".
Potremmo quindi sovvrascrivere il metodo interrupt nella nostra classe per settare il nostro flag a false, provocando quindi la terminazione del ciclo.
(In marroncino le modifiche rispetto al precedente codice di CiofecaThread)
public class IterruptibleThread extends Thread
{
private boolean runFlag = true;
@Override
public void run()
{
System.out.println("<Thread>loop started.");
while (runFlag) {
// magia: ciuccia la CPU facendo assolutamente niente
// tranne girare come un criceto impazzito nella sua ruota.
}
// istruzione ora raggiungibile:
System.out.println("<Thread>loop ended.");
}
@Override
public void interrupt()
{
// prima chiamiamo interrupt della classe padre, Thread
// per lasciare inalterato il suo comportamento
super.interrupt();
// quindi settiamo il flag per terminare il ciclo
runFlag = false;
}
public static void main(String... args) {
IterruptibleThread t = new IterruptibleThread();
t.start();
System.out.println("<Main>thread launched!");
System.out.println("<Main>sleeping...");
sleep(3000);
System.out.println("<Main>sending interrupt...");
t.interrupt();
}
public static void sleep(long millis)
{
try {
Thread.sleep(millis);
}
catch (InterruptedException ignored) {/*do nothing*/}
}
}
banryu79
26-03-2010, 11:11
Proviamo ora a supportare l'interruzione con la tecnica descritta al punto (1), cioè sfruttando il meccanismo di interrupt che la classe Thread ci mette già a disposizione (invece di definire noi un nostro flag, sfruttiamo il flag interno per gli interrupt di Thread).
L'idea è quella di controllare se è arrivato un interrupt (come al solito, provocato da una chiamata al meotdo interrupt) andando a vedere se il flag interno è stato settato.
Thread è una classe che mette già a disposizione dei metodi per interrogare lo stato di questo flag ed eventualmente resettarlo.
Ad esempio, il metodo statico Thread.interrupted() torna true se il thread corrente (quello che esegue la chiamta Thread.interrupted()) ha ricevuto una richiesta di interruzione, e resetta automaticamente il flag.
Nel nostro caso, possiamo usare questa possibilità di controllo&reset del flag di interrupt per condizionare l'esecuzione del ciclo while:
(le parti rilevanti sono in marroncino)
public class InterruptibleThread2 extends Thread
{
@Override
public void run()
{
System.out.println("<Thread>loop started.");
while (! Thread.interrupted()) {
// magia: ciuccia la CPU facendo assolutamente niente
// tranne girare come un criceto impazzito nella sua ruota.
}
// istruzione ora raggiungibile:
System.out.println("<Thread>loop ended.");
}
public static void main(String... args) {
InterruptibleThread2 t = new InterruptibleThread2();
t.start();
System.out.println("<Main>thread launched!");
System.out.println("<Main>sleeping...");
sleep(3000);
System.out.println("<Main>sending interrupt...");
t.interrupt();
}
public static void sleep(long millis)
{
try {
Thread.sleep(millis);
}
catch (InterruptedException ignored) {/*do nothing*/}
}
}
banryu79
26-03-2010, 13:18
Ultimo esempio su cui riflettere, il caso (2).
Quello, cioè, in cui si supporta l'interruzione di un thread durante la chiamata a un metodo che potrebbe lanciare una InterruptedException:
package countdown;
public class InterruptibleThread3 extends Thread
{
@Override
public void run()
{
System.out.println("<Thread> started.");
try {
Thread.sleep(150000);
}
catch (InterruptedException ie) {
System.out.println("<Thread> interrupted!");
}
finally {
System.out.println("<Thread> ended.");
}
}
public static void main(String... args) {
InterruptibleThread3 t = new InterruptibleThread3();
t.start();
System.out.println("<Main> thread 't' launched!");
System.out.println("<Main> sleeping...");
sleep(3000);
System.out.println("<Main> sending interrupt to 't'");
t.interrupt();
}
public static void sleep(long millis)
{
try {
Thread.sleep(millis);
}
catch (InterruptedException ignored) {/*do nothing*/}
}
}
Qui il nostro thread non fa altro che mettersi a dormire per 15 sec. circa.
Nel frattempo potrebbe arrivargli un'interrupt: in tale caso il metodo sleep spara una InterruptedException e si può quindi gestire l'interrupt nella relativa clausola catch.
Facciamo ora un esempio un po' più articolato.
Supponiamo che il nostro thread debba eseguire due diverse operazioni, una che richiede un certo tempo (tramite metodo Thread.sleep) e subito dopo un'altra, che è un loop infinito.
Naturalmente vogliamo che il nostro thread supporti gli interrupt.
Deve quindi supportare le interruzioni sia durante l'esecuzione della prima operazione, che durante la seconda.
Dunque ecco una versione provvisoria, che usiamo per studiare la situazione, del metodo run:
public class InterruptibleThread4 extends Thread
{
@Override
public void run()
{
System.out.println("<Thread> started.");
System.out.println("<Thread> operation_1");
try {
Thread.sleep(7000);
}
catch (InterruptedException ie) {
System.out.println("<Thread> interrupted!");
return;
}
System.out.println("<Thread> operation_2");
while (true) {
// ciclo infinito
if (Thread.interrupted()) {
System.out.println("<Thread> interrupted!");
return;
}
}
}
...
}
Funziona, però, supponendo di avere non solo due, ma diverse operazioni da eseguire nel thread, il codice del metodo run comincerebbe a diventare lunghetto e perderebbe di leggibilità.
Il fatto è che se si vuole supportare l'interrupt per una data operazione, il codice per farlo sembra "assumere due diverse forme": un blocco try-catch in un caso, un controllo esplicito del flag di interrupt del Thread in una condizione di un controllo di flusso che ne gestisce l'interrupt, nell'altro.
Sarebbe bello poter gestire la cosa in un unico modo, e magari evitare le duplicazioni di codice di gestione dell'interrupt (in corsivo, nel codice).
Se incapsuliamo la seconda operazione (il ciclo "infinito") in un metodo che lancia una InterruptedException, il gioco è fatto perchè possiamo, nel metodo run, utilizzare un unico blocco try-catch per gestire tutte le operazioni.
Per uniformità incapsuliamo in un metodo analogo anche la chiamata a Thread.sleep().
Il risultato è questo:
package countdown;
public class InterruptibleThread4 extends Thread
{
@Override
public void run()
{
System.out.println("<Thread> started.");
try {
operation1();
operation2();
}
catch (InterruptedException ie) {
System.out.println("<Thread> interrupted!");
}
finally {
System.out.println("<Thread> ended.");
}
}
private void operation1() throws InterruptedException
{
System.out.println("<Thread> operation_1, start.");
Thread.sleep(4000);
System.out.println("<Thread> operation_1, end.");
}
private void operation2() throws InterruptedException
{
System.out.println("<Thread> operation_2, infinite...");
while(true) {
// ciclo infinito
if (Thread.interrupted())
throw new InterruptedException();
}
}
public static void main(String... args) {
InterruptibleThread4 t = new InterruptibleThread4();
t.start();
System.out.println("<Main> thread 't' launched!");
System.out.println("<Main> sleeping...");
sleep(8500);
System.out.println("<Main> sending interrupt to 't'");
t.interrupt();
}
public static void sleep(long millis)
{
try {
Thread.sleep(millis);
}
catch (InterruptedException ignored) {/*do nothing*/}
}
}
banryu79
26-03-2010, 14:30
Fammi sapere se riesci a digerire tutto l'ambaradam qui sopra o se ci sono cose che ti risultano poco chiare, mi rendo conto che potrebbero esserci diversi aspetti da chiarire, e resto a disposizione.
In teoria, a digestione compiuta, dovresti essere in grado di poter valutare autonomamente come supportare l'interrupt nella classe Counter, in Countdown (lasciando perdere le questioni relative all'interfaccia grafica, e considerando solo la classe che implementa il countdown).
Metto le mani avanti a mia discolpa: non essendo un insegnante ne sapendo nulla di didattica non so se ti sto dando una mano o in effetti se ti sto incasinando... :stordita:
Ti ringrazio per l ennesima volta.
Per ora non sono molto presente a casa per problemi di università, ma ben presto mi metterò a lavorare al progetto.
Se ci riesco o se ho problemi ti avviso!
A presto ! Buon fine settimana! :D
Buonasera,
dopo tanto tempo mi sono ributtato nel progetto...ed alla fine mi sbrigato mettendo al Ferma Timer un bel System.exit(0) ... Ti ringrazio di cuore!
Alla prossima :D ed AUGURI PER PASQUAAAAA! :D
banryu79
06-04-2010, 11:30
Buonasera,
dopo tanto tempo mi sono ributtato nel progetto...ed alla fine mi sbrigato mettendo al Ferma Timer un bel System.exit(0) ... Ti ringrazio di cuore!
Ciao, mi stavo perdendo il thread...
Alla prossima :D ed AUGURI PER PASQUAAAAA! :D
Auguri anche a te, in ritardo
Comunque, casomai tornassi per questi lidi e ti interessasse, un modo per gestire l'interruzione (senza uccidere applicazione e virtual machine, dato che è appena passata la Pasqua :D ) potrebbe essere questo:
Il codice:
Countdown counter;
...
public void actionPerformed(ActionEvent e) {
String command = e.getActionCommand();
if (command.equals("Avvia Timer")) {
long time = Long.parseLong(timer.getText());
counter = new Countdown(time);
counter.addListener(new CountdownListener(){
public void countdownEnded(CountdownEvent evt) {
String caricaTxt = evt.endedNormally()
? "CARICA COMPLETATA!"
: "CARICA INTERROTTA!";
carica.setText(caricaTxt);
}
});
carica.setText("BATTERIA IN CARICA...");
counter.start();
}
if (command.equals("Ferma Timer")) {
counter.interrupt();
}
}
Per ottenerlo bisogna gestire l'evento "Il countdown è terminato" distinguendo il caso in cui esso termini perchè "ha finito di contare" (terminazione normale) oppure perchè "ha ricevuto un interrupt" (interruzione).
Per supportare questa distinzione (che, come vedi nel codice sopra, viene sfruttata per decidere cosa stampare come etichetta sulla label 'carica') ho aggiunto una nuova entità/classe (chiamata CountdownEvent) al meccanismo precedente di notifica, che vedeva protagonisti la classe Countdown e CountdownListener.
Ora Countdown passa a tutti i CountdownListener registrati un CountdownEvent per notificare che esso (Countdown) è terminato, permettendo di distingue il caso in cui la terminazione sia dovuta ad un'interruzione, e, come prima, informare del tempo trascorso in sec.
Ecco CountdownEvent:
package countdown;
/**
*
* @author Francesco
*/
public class CountdownEvent
{
public static final int END_NORMALLY = 100;
public static final int END_INTERRUPTED = 101;
private float seconds;
private int endType;
public CountdownEvent(float secs, int type)
{
seconds = secs;
endType = type;
}
public float elapsedSeconds()
{
return seconds;
}
public boolean endedNormally()
{
return endType == CountdownEvent.END_NORMALLY;
}
}
L'interfaccia CountdownListener è cambiata:
package countdown;
public interface CountdownListener
{
/**
* Invoked when the countdown ended.
* @param evt the countdown event notified
*/
public void countdownEnded(CountdownEvent evt);
}
Infine, ecco le modifiche apportate a Countdown in seguito all'introduzione di CountdownEvent:
package countdown;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
/**
* Un conto alla rovescia, non bloccante, che notifica tutti gli ascoltatori registrati al passaggio del
* prefissato intervallo di tempo, dall'avvio del countdown.
* Può essere interrotto.
*
* @author Francesco
*/
public class Countdown
{
final private long DELAY;
final private Thread COUNTER_THREAD;
// listerns support for countdown events
private Collection<CountdownListener> listeners;
/**
* Construct a countdown that count for delay milliseconds
* @param delay the delay, in millisec
*/
public Countdown(long delay)
{
DELAY = delay;
COUNTER_THREAD = new Thread(new Counter());
}
/**
* Start the countdown (non-blocking)
*/
public void start()
{
COUNTER_THREAD.start();
}
/**
* Interrupt the countdown
*/
public void interrupt()
{
COUNTER_THREAD.interrupt();
}
/**
* Register l for countdown events.
* @param l the COuntdownListener to be registered.
*/
public void addListener(CountdownListener l)
{
// the listener must not be null
if (l == null) {
return;
}
// init listeners
if (listeners == null) {
listeners = new HashSet<CountdownListener>(10);
}
listeners.add(l);
}
/**
* Deregister all listeners
*/
public void removeAllListeners()
{
Iterator<?> it = listeners.iterator();
while (it.hasNext()) it.remove();
}
protected void notifyListeners(float seconds, int endAttribute)
{
CountdownEvent evt = new CountdownEvent(seconds, endAttribute);
Iterator<CountdownListener> it = listeners.iterator();
while (it.hasNext()) {
CountdownListener l = it.next();
l.countdownEnded(evt);
}
}
/**
* Simple countdown implementation, wait for DELAY millisec.
* If the countdown thread is interrupted immediatly end the countdown and notify all listeners, for
* semplicity.
* Also notify the listeners about the elapsed time.
*/
class Counter implements Runnable
{
public void run() {
int endType = CountdownEvent.END_NORMALLY;
long started = System.nanoTime();
try {
Thread.sleep(DELAY);
}
catch(InterruptedException ex) {
endType = CountdownEvent.END_INTERRUPTED;
}
finally {
float elapsed = (System.nanoTime()-started)/1000000000f;
notifyListeners(elapsed, endType);
}
}
}
}
^TiGeRShArK^
06-04-2010, 16:29
mmm...
io mi sono messo a giochicchiare un pò per risolverlo utilizzando le classi Timer e TimerTask delle JFC visto che era da un pò che non toccavo java. :p
public class TimerFrame extends JFrame implements ActionListener {
private JLabel label;
private Timer timer;
private JButton startButton;
private JButton stopButton;
private int counter = 0;
public TimerFrame() throws HeadlessException {
JPanel panel = new JPanel();
label = new JLabel(" ");
startButton = new JButton("Avvia");
startButton.addActionListener(this);
stopButton = new JButton("Ferma");
stopButton.addActionListener(this);
add(panel);
panel.add(label);
panel.add(startButton);
panel.add(stopButton);
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == startButton) {
if (timer != null) {
return;
}
timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
label.setText("" + counter);
}
});
counter++;
}
}, 0, 1000);
} else {
if (timer == null) {
return;
}
timer.cancel();
timer = null;
counter = 0;
}
}
}
e questo è la main class per istanziare il frame:
public class Prova {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
TimerFrame frame = new TimerFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
});
}
}
Ad occhio mi pare che funzioni. :p
banryu79
06-04-2010, 16:50
mmm...
io mi sono messo a giochicchiare un pò per risolverlo utilizzando le classi Timer e TimerTask delle JFC visto che era da un pò che non toccavo java. :p
Ottimo, così l'utente ciccio5 ha anche un altro esempio.
I post-papiro servivano solo per (spero) aiutare cicco5 nei ragionamenti, premettendo che a bella posta si stava ignorando java.swing.Timer e java.util.Timer, allo scopo di implementare un "conto alla rovescia" sfruttando il meccanismo di interrupt (la prima cosa che mi è saltata in testa) della classe Thread.
P.S.: come sarebbe che non toccavi java da un po'? In che lidi sguazzi? :D
^TiGeRShArK^
06-04-2010, 16:56
Ottimo, così l'utente ciccio5 ha anche un altro esempio.
I post-papiro servivano solo per (spero) aiutare cicco5 nei ragionamenti, premettendo che a bella posta si stava ignorando java.swing.Timer e java.util.Timer, allo scopo di implementare un "conto alla rovescia" sfruttando il meccanismo di interrupt (la prima cosa che mi è saltata in testa) della classe Thread.
P.S.: come sarebbe che non toccavi java da un po'? In che lidi sguazzi? :D
Sono passato definitivamente al lato oscuro del C# + WPF per ora e prima comunque utilizzavo java solo per la consulenza con la regione e per un paio di lavoretti su AS400 :D
Ho visto che utilizzando propriamente le WPF si ottengono interfacce grafiche decisamente + pulite (dal punto di vista del codice e della separazione tra i layer)..
peccato che io sia ancora abbastanza novellino con WPF dato che ci lavoro da solo un mese e mezzo e prima ci avevo solo giochicchiato....
Quasi quasi provo a fare l'equivalente in WPF, vediamo che esce :D
^TiGeRShArK^
06-04-2010, 19:10
come promesso :p
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Grid>
<StackPanel Orientation="Vertical">
<StackPanel.DataContext>
<local:TimerVM/>
</StackPanel.DataContext>
<TextBlock Text="{Binding Counter}"/>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Content="Avvia" Command="{Binding StartCommand}" Width="80"/>
<Button Content="Ferma" Command="{Binding StopCommand}" Width="80"/>
</StackPanel>
</StackPanel>
</Grid>
</Window>
namespace WpfApplication1
{
public class TimerVM : DependencyObject
{
public int Counter
{
get { return (int)GetValue(CounterProperty); }
set { SetValue(CounterProperty, value); }
}
public static readonly DependencyProperty CounterProperty =
DependencyProperty.Register("Counter", typeof(int), typeof(TimerVM), new UIPropertyMetadata(0));
private DispatcherTimer timer;
public Command StartCommand { get; set; }
public Command StopCommand { get; set; }
public TimerVM()
{
StartCommand = new Command();
StartCommand.Execute = () =>
{
if (timer != null) return;
Counter = 0;
timer = new DispatcherTimer();
timer.Tick += new EventHandler((object sender, EventArgs e) => Counter++);
timer.Interval = new TimeSpan(0, 0, 1);
timer.Start();
};
StopCommand = new Command();
StopCommand.Execute = () =>
{
if (timer == null) return;
timer.Stop();
timer = null;
};
}
}
public class Command : ICommand
{
public Action Execute;
public event EventHandler CanExecuteChanged;
public bool CanExecute(object o) { return true; }
void ICommand.Execute(object o) { Execute(); }
}
}
Devo dire che mi piace decisamente di + rispetto alla controparte java e in questo modo la View è completamente slegata dal ViewModel. :p
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.