Problema: Jbutton con evento associato al thread SwingWorker

di il
3 risposte

Problema: Jbutton con evento associato al thread SwingWorker

Salve come da titolo vorrei fare in modo di poter implementare 2 bottoni che hanno associato un evento che consiste di invocare una wait() sul worker (thread SwingWorker) e una notify() sull'altro bottone per poter simulare la pausa e il resume di un gioco.
Sto tentando già da un pò e non riesco a capire dove sto sbagliando, perchè con il codice sottostante ottengo l'eccezione: IllegalMonitorStateException.

public class FinestraGioco {
	private JFrame frame;
	static JLabel [][] grid = new JLabel[10][10];
	SwingWorker<Matrice,Matrice> worker;
	
	private void startThread() {
		this.worker = new SwingWorker<Matrice,Matrice>(){ 
			@Override
			protected Matrice doInBackground() throws Exception{
				Matrice matrice1 = new Matrice();
				matrice1.generaElementi();
				for ( ;;) {
					Thread.sleep(10000);
					for(int i = 0 ; i < 30 ; i++) {
						matrice1.itera();
						publish(matrice1);
						Thread.sleep(10000);
					}
					matrice1.resettaMatrice();
					matrice1.generaElementi();
				}
			}
			@Override
			protected void done() { //completato gioco
				try {
					Matrice exitStatus = get();
					System.out.println("recuperato lo stato");
				}catch(InterruptedException e) {
					e.printStackTrace();
				}catch(ExecutionException e) {
					e.printStackTrace();
				}
			}
			@Override
			protected void process(List<Matrice> chunks) { //arrivano tutti i publish nella lista
				Matrice aggiornamento = chunks.get(chunks.size() -1);
				Object[][] matrix = aggiornamento.getMatrix();
				for(int i = 0 ; i < matrix.length;i++) {
					for ( int j = 0 ; j < matrix[i].length;j++) {
						if(matrix[i][j] instanceof Auto){
						Auto auto = Auto.getIstance();
						grid[i][j].setBackground(auto.getColore());
					}else if(matrix[i][j] instanceof Ostacolo) {
						grid[i][j].setBackground(Color.CYAN);
					}else if(matrix[i][j] instanceof Oggetto) {
						grid[i][j].setBackground(Color.ORANGE);
					}else if(matrix[i][j] == null){
						grid[i][j].setBackground(Color.GRAY); 
						}
					}
				}			
			}

		};
		worker.execute();
	}
		
	public void pausa() throws InterruptedException{
		 this.worker.wait();
	}
	
	public  void resume(){
		 this.worker.notify();
	} 
Il costruttore della classe esegue il metodo inizialize() per costruire la GUI dove all'interno di quest'ultima ho definito i 2 bottoni:

JButton button_1 = new JButton("Pause Game");
		button_1.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				try {
					pausa();
				} catch (InterruptedException e1) {
					e1.printStackTrace();
				}
			} 
		});
		button_1.setBounds(512, 21, 117, 41);
		button_1.setBackground(Color.GREEN);
		button_1.setFont(f1);
		frame.getContentPane().add(button_1);
		
		JButton btnNewButton_1 = new JButton("Resume Game");
		btnNewButton_1.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				resume();
			} 
infine per far partire il thread di background ho inserito all'interno di inizialize() :

//inizio background
		startThread();
Con questo spero che qualcuno sappia darmi una dritta su come posso risolvere questo problema.
Tutto il codice postato si trova all'interno di un unica classe (FinestraGioco).

3 Risposte

  • Re: Problema: Jbutton con evento associato al thread SwingWorker

    SkyFallr ha scritto:


    Salve come da titolo vorrei fare in modo di poter implementare 2 bottoni che hanno associato un evento che consiste di invocare una wait() sul worker (thread SwingWorker) e una notify() sull'altro bottone per poter simulare la pausa e il resume di un gioco.
    Ci sono diverse cose da chiarire: innanzitutto i wait()/notify() degli oggetti NON sono stati fatti per essere usati come meccanismo di sospensione/riesumazione di un thread in generale, non così banalmente come hai fatto tu. E comunque l'utilizzo NON è nemmeno corretto/sensato, perché innanzitutto per poter invocare wait/notify bisogna possedere il lock dell'oggetto su cui sono invocati. Serve la parola chiave synchronized e nel tuo codice non la vedo nemmeno ....

    In ogni caso i wait/notify sono sull'oggetto SwingWorker ma questo non c'entra niente direttamente con il thread in background. SwingWorker è solo un Runnable (RunnableFuture per la precisione) ma non è il Thread! Quindi anche se l'uso di wait/notify fosse tecnicamente corretto, non avrebbe alcun effetto sul thread-lavoro.

    Infine il concetto su sospensione/riesumazione. Per poter gestire questo, il lavoro (il TUO lavoro, quello che fai nel doInBackground() ) deve essere "cooperativo" alla sospensione. Vuol dire che di tanto in tanto, in punti e momenti del codice che puoi decidere tu, ci deve essere qualcosa che si chiede sostanzialmente "devo sospendermi?", se sì sta lì finché altro non lo riattiva, se no continua.
    Il tuo lavoro che vedo (non so cosa fa Matrice naturalmente) non mi pare che sia affatto cooperativo in tal senso.

    Insomma, è tutto da rivedere e rifare ...
  • Re: Problema: Jbutton con evento associato al thread SwingWorker

    Grazie per la risposta!
    Io per le direttive che devo rispettare nell'esercizio devo fare in modo che gli elementi in una matrice si spostino seguendo determinate strategie che sono implementate nei metodi che invoco in sequenza , questa parte di codice viene gestita da un solo thread e la sincronizzazione in mutua esclusione non mi serve in quanto è solo un thread che si occupa iterativamente delle operazioni ( in doInBackground() ).
    Però sono vincolato a mettere nella mia GUI delle opzioni per l'utente che "vedendo" l'esecuzione del gioco in maniera automatica possa comunque fare delle azioni che sono gestite dall'EDT.
    Mi è parso di capire che potrei associare, per esempio al pulsante pausa l'evento di una chiamata a un metodo che setti un valore booleano che venga poi controllato all'interno del metodo doInBackground() (o all'interno dei metodi invocati all'interno) e se questa variabile è true si blocca (wait() ), questo significa che praticamente devo avere qualcun' altro che possa sbloccare (notify() ), giusto? Se si questo notify potrebbe essere fatto fare dall'EDT?
  • Re: Problema: Jbutton con evento associato al thread SwingWorker

    Quello di seguito è un esempio che ho appena scritto per mostrare come gestire correttamente sospensione/ripresa in uno SwingWorker.
    import java.awt.FlowLayout;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.SwingUtilities;
    import javax.swing.SwingWorker;
    
    public class FrameProva extends JFrame {
        public FrameProva() {
            super("Prova Task");
            setDefaultCloseOperation(EXIT_ON_CLOSE);
            setSize(300, 200);
            setLocationRelativeTo(null);
            setLayout(new FlowLayout());
    
            JButton btnSospendi = new JButton("SOSPENDI");
            JButton btnRiprendi = new JButton("RIPRENDI");
            add(btnSospendi);
            add(btnRiprendi);
    
            TaskProva task = new TaskProva();
    
            btnSospendi.addActionListener(e -> task.setSospeso(true));
            btnRiprendi.addActionListener(e -> task.setSospeso(false));
    
            task.execute();
        }
    
        public static void main(String[] args) {
            SwingUtilities.invokeLater(() -> new FrameProva().setVisible(true));
        }
    }
    
    
    class TaskProva extends SwingWorker<Void, Void> {
        private final Object lockSospeso = new Object();
        private volatile boolean sospeso;
    
        @Override
        protected Void doInBackground() throws Exception {
            for (int i = 1; i <= 1000; i++) {
                checkSospeso();
    
                System.out.println("giro " + i);
                Thread.sleep(250);
            }
    
            return null;
        }
    
        public void setSospeso(boolean sospeso) {
            synchronized (lockSospeso) {
                this.sospeso = sospeso;
                lockSospeso.notify();
            }
        }
    
        private void checkSospeso() throws InterruptedException {
            if (sospeso) {
                synchronized (lockSospeso) {
                    while (sospeso) {
                        lockSospeso.wait();
                    }
                }
            }
        }
    }
    Ci sono diverse cose da notare:
    - ho usato un oggetto di lock specifico (lockSospeso) perché ho visto nel sorgente di SwingWorker che usa già synchronized (this) per alcune sincronizzazioni interne e quindi ho preferito NON riusare l'oggetto SwingWorker come "lock" anche per la sospensione.
    - la variabile booleana sospeso è volatile. Questo è importante per il test su sospeso messo "fuori" dalla sincronizzazione nel checkSospeso() per rendere il check più efficiente.
    - il checkSospeso() l'ho messo private, è ad uso "interno" del task che lo deve usare in tutti i punti dove si può/vuole rendere sospendibile il lavoro

    Esegui pure il programma e prova i due pulsanti per vedere l'effetto su standard-output.


    P.S. ho usato le lambda (Java 8+) solo per brevità, non sono obbligatorie.
Devi accedere o registrarti per scrivere nel forum
3 risposte