Visualizzazione dei risultati da 1 a 4 su 4
  1. #1
    Utente di HTML.it L'avatar di ziz
    Registrato dal
    Jun 2008
    Messaggi
    52

    Thread concorrenti Swing

    Ciao,

    sto realizzando un programmino in Java/Swing e sto incontrando delle difficoltà nella realizzazione dell'ultimo step.

    In pratica il mio programma si occupa di analizzare un file XML e riportarlo in forma più leggibile in un file XLS.

    Dato che i file da elaborare sono abbastanza pesanti, vorrei fare in modo che all'utente vengano mostrati gli avanzamenti dell'elaborazione, almeno in forma testuale (ad es. "Lettura file completata", "Ordinamento campi in corso...", ecc...)

    Dopo aver perso due giorni in ricerche varie e implementazione di possibili soluzioni, ho modificato come segue il main:
    codice:
    public static void main(String[] args) {
    		
    	SwingUtilities.invokeLater(new Runnable() {
    		public void run() {
    			REMExtractorGui gui = new REMExtractorGui();
    			gui.buildGUI();
    		}
    	});
    }
    Alla pressione di un bottone, partono lettura del file, ordinamento dei risultati e creazione del file Excel.
    Riporto di seguito il metodo ActionPerformed del listener:
    codice:
    public void actionPerformed(ActionEvent e) {
    	long start = System.currentTimeMillis();
    			
    	try {
    		if(fileNameTextField.getText() == null || fileNameTextField.getText().isEmpty()) {
    			JOptionPane.showMessageDialog(frame, "Devi selezionare il file per proseguire!", "ATTENZIONE", JOptionPane.WARNING_MESSAGE);
    		}
    		else {
    					
    			extractor = new Extractor(dateFromPicker.getDate(),
    						dateToPicker.getDate());
    					
    			JDialog dialog = new JDialog(frame);
    			JPanel dialogPanel = new JPanel();
    			dialogPanel.setLayout(new BoxLayout(dialogPanel, BoxLayout.Y_AXIS));
    			dialogPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
    					
    			dialog.add(dialogPanel);
    			dialog.setTitle("Elaborazione...");
    			dialog.setSize(200, 170);
    			//dialog.pack();
    			dialog.setLocationRelativeTo(null);
    			dialog.setVisible(true);
    					
    			extractor.readFile(new File(fileNameTextField.getText()));
    					
    			Collections.sort(extractor.getData());
    					
    			WriteExcel excel = new WriteExcel("c:/temp/NEW.xls");
    			excel.write(extractor.getData());
    							
    			dialog.setTitle("Esito");
    					
    			JLabel results = new JLabel("Elaborazione terminata con successo");
    			dialogPanel.add(results);
    					
    			JLabel executionTime = new JLabel("Tempo di esecuzione: " + (System.currentTimeMillis() - start)/1000.0 + " sec.");
    			executionTime.setHorizontalAlignment(JLabel.LEFT);
    			dialogPanel.add(executionTime);
    			JLabel numData = new JLabel("Dati elaborati: " + extractor.getData().size());
    			numData.setHorizontalAlignment(JLabel.LEFT);
    			dialogPanel.add(numData);
    					
    			dialog.repaint();
    			dialog.validate();
    							
    		}
    	}
    	catch(FileNotFoundException fileNotFoundEx) {
    		fileNotFoundEx.printStackTrace();
    		JOptionPane.showMessageDialog(frame, "Non è stato possibile trovare il file specificato. Impossibile continuare.", "ERRORE", JOptionPane.ERROR_MESSAGE);
    	}
    	catch(XMLStreamException xmlStreamEx) {
    		xmlStreamEx.printStackTrace();
    		JOptionPane.showMessageDialog(frame, "Si è verificato un errore durante l'elaborazione del file. Impossibile continuare.", "ERRORE", JOptionPane.ERROR_MESSAGE);
    	}
    	catch(Exception ex) {
    		ex.printStackTrace();
    		JOptionPane.showMessageDialog(frame, "E' stato rilevato un errore generico. Impossibile continuare.", "ERRORE", JOptionPane.ERROR_MESSAGE);
    	}
    	// Validate per aggiornare il posizionamento degli elementi
    	frame.validate();
    	// Repaint per aggiornare il testo delle label
    	frame.repaint();
    }
    Ho capito che devo utilizzare un thread anche in questo secondo metodo per lasciare l'elaborazione della grafica al thread principale di Swing, ma non mi è molto chiara la logica. Se lancio in un thread a parte il mio estrattore, come faccio a recuperare le eccezioni e a far visualizzare i relativi pop-up? Mi sto perdendo qualcosa?

    Grazie per il supporto,
    Francesco

  2. #2
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,284

    Re: Thread concorrenti Swing

    Originariamente inviato da ziz
    ho modificato come segue il main:
    codice:
    public static void main(String[] args) {
    		
    	SwingUtilities.invokeLater(new Runnable() {
    		public void run() {
    			REMExtractorGui gui = new REMExtractorGui();
    			gui.buildGUI();
    		}
    	});
    }
    Ok, ma questa è solo la "buona" pratica di far partire tutto, fin dall'inizio, nel EDT (Event Dispatch Thread). Cioè vale in generale nelle applicazioni Swing.

    Originariamente inviato da ziz
    Alla pressione di un bottone, partono lettura del file, ordinamento dei risultati e creazione del file Excel.

    Ho capito che devo utilizzare un thread anche in questo secondo metodo per lasciare l'elaborazione della grafica al thread principale di Swing, ma non mi è molto chiara la logica. Se lancio in un thread a parte il mio estrattore, come faccio a recuperare le eccezioni e a far visualizzare i relativi pop-up? Mi sto perdendo qualcosa?
    Sì, se il lavoro impiega (potenzialmente) molto tempo, devi eseguirlo in un thread separato, per non tenere "impegnato" troppo a lungo il EDT, cosa che causerebbe il "congelamento" della tua interfaccia utente.

    Nel contesto di un altro thread però non devi accedere direttamente alla interfaccia utente (quindi niente setText, setTitle, contenitore.add(componente), ecc...). Salvo poche operazioni, ben note e documentate che sono thread-safe, per il resto Swing non è thread-safe e l'accesso alla interfaccia utente va fatto solo nel EDT.

    Nessuno però vieta, nel tuo thread a parte, di usare invokeLater/invokeAndWait di SwingUtilities per far eseguire un pezzetto di codice nel EDT che permetterebbe di fare appunto quel "qualcosa" (es. un label.setText o un dialog.setTitle) in modo appropriato e sicuro per Swing.

    Il punto è che se nel tuo thread ti ritrovi a dover fare innumerevoli volte e in vari punti dei invokeLater/invokeAndWait e magari solo per aggiornare la stessa cosa, evidentemente il design non è granché e devi cambiarlo ...

    Da Java 6 in javax.swing c'è SwingWorker, è una classe apposta per far eseguire qualcosa in un thread separato ma con la possibilità di avere dei "punti" di incontro con il EDT senza dover ripetere ogni volta gli invokeXXX. Per usare SwingWorker devi però comprendere bene il suo workflow (ci sono documentazione/tutorial ufficiali).

    Se SwingWorker non puoi/vuoi usarlo, allora devi realizzare tu un design che permetta di separare bene le cose, facendo in modo che sia semplice (per il thread) aggiornare la interfaccia utente in modo appropriato e senza troppo codice ripetitivo.

    Se ad esempio ti servisse fare N lavori diversi ma tutti hanno in comune il fatto di usare quella stessa dialog di monitor della attività, allora puoi creare una classe base che crea la dialog, crea il thread e offre i servizi (in modo thread-safe) per aggiornarla, in modo che una sottoclasse deve solo definire il "lavoro" specifico e richiamare quei metodi di servizio.
    Ripeto: qui si va più su questioni di "design" delle classi che altro.

    Se vuoi un esempio, chiedi.
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    java.util.function Interfaces Cheat SheetJava Versions Cheat Sheet

  3. #3
    Utente di HTML.it L'avatar di ziz
    Registrato dal
    Jun 2008
    Messaggi
    52
    Ciao Andbin,

    grazie mille per la risposta. Quindi per il momento la strada più semplice potrebbe essere quella di generare un nuovo thread per l'elaborazione e inserire un listener nella GUI che mi aggiorni il dialog.

    Tuttavia sarei interessato a capire meglio il funzionamento dello SwingWorker.

    Ho provato a vedere la documentazione ufficiale e cercato di trovare una soluzione per il mio problema.

    Credi che qualcosa di questo tipo possa andar bene? Da quanto ho letto credo possa funzionare, ma non so se ha senso utilizzare uno SwingWorker<Void, Void>...

    codice:
    SwingWorker worker = new SwingWorker<Void, Void>() {
        @Override
        public void doInBackground() {
            String[] esitiDaStampare = new String[6];
    
            esitiDaStampare[0] = "Lettura in corso...";
            publish(esitiDaStampare);
            elaborazioneLettura();
            esitiDaStampare[0] = "Lettura in corso...Completata!";
            publish(esitiDaStampare);
    
            esitiDaStampare[1] = "Ordinamento in corso...";
            publish(esitiDaStampare);
            elaborazioneLettura();
            esitiDaStampare[1] = "Ordinamento in corso...Completata!";
            publish(esitiDaStampare);
    
            //ecc...
            
            return null;
        }
    
        @Override
        protected void process(List<String[]> labels) {
            String[] label = labels.get(labels.size() - 1);
            label1.setText(label[0]);
            label2.setText(label[1]);
            label3.setText(label[2]);
            / ecc...
        }
    };
    Grazie ancora,
    Francesco

  4. #4
    Utente di HTML.it L'avatar di andbin
    Registrato dal
    Jan 2006
    residenza
    Italy
    Messaggi
    18,284
    Originariamente inviato da ziz
    inserire un listener nella GUI che mi aggiorni il dialog.
    Dipende cosa intendi per "listener" (se esattamente come lo intendono AWT/Swing) ma credo che in questo contesto sia un'ottica sbagliata.

    Originariamente inviato da ziz
    Tuttavia sarei interessato a capire meglio il funzionamento dello SwingWorker.

    Credi che qualcosa di questo tipo possa andar bene? Da quanto ho letto credo possa funzionare, ma non so se ha senso utilizzare uno SwingWorker<Void, Void>...
    No, non ha senso e il codice che hai postato non dovrebbe nemmeno compilarti!

    In SwingWorker<T,V>, il T è il tipo che deve restituire doInBackground(). Questo è un valore "finale" ritornato dalla computazione in background e che può essere ottenuto dai metodi "bloccanti" get (se necessario, attendono la fine della computazione).

    Certo, non sempre è necessario e se a te non serve, devi comunque usare Void (sia nella parametrizzazione per T sia nella dichiarazione di doInBackground), non void.

    Il V è il tipo che può essere "pubblicato" con publish e che viene passato al process, nascondendo però (a questo serve SwingWorker) il passaggio nel EDT che viene fatto internamente.

    Pertanto se hai scritto process(List<String[]> labels), non puoi certo tipizzare V con Void!!
    Tra l'altro credo che non hai capito un'altra cosa: se devi "pubblicare" N String, ti basta che V sia String ... non String[]. publish è un metodo "varargs", che accetta direttamente N argomenti o in alternativa un array diretto. E gli N valori li riceve il process con un List.

    P.S.: e occhio alla questione, ben spiegata nella documentazione di publish, che più "pubblicazioni" potrebbero essere accorpate insieme in una unica invocazione a process.
    Andrea, andbin.devSenior Java developerSCJP 5 (91%) • SCWCD 5 (94%)
    java.util.function Interfaces Cheat SheetJava Versions Cheat Sheet

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2025 vBulletin Solutions, Inc. All rights reserved.