Thread e gui javaFX

di il
6 risposte

Thread e gui javaFX

Salve a tutti,
sto frequentando un corso da programmatore e sto trovando qualche problema con l'uso dei Thread.
Sto facendo un esercizio in cui tramite un'interfaccia javaFX prendo un file *.txt e lo scrivo su una textArea della mia form. Inoltre il progresso è segnalato in una progressBar.
Sto usando i Task di javaFx per il multithread, ma continuo a ricevere eccezioni sul thread principale di javaFx.
Posto un po' di codice:
public class ControlInterfaccia {
	
	@FXML
	private TextField percorso;
	@FXML
	private TextArea scaricati;
	@FXML
	volatile private TextArea preview;
	@FXML
	private ProgressBar barra;
	public void initialize() {
		percorso.setText(System.getProperty("user.home")+"\\Desktop");
	}
	@FXML
	private void browser(){
		[omiss...
                altro metodo per il bottone sfoglia]
	}
	@FXML
	private void download() throws FileNotFoundException{
		LetturaFile let=new LetturaFile(new File(percorso.getText()), preview);
		Task<Void> task=new Task<Void>() {

			@Override
			protected Void call() throws Exception {
				while(!let.fineFile()){
					let.leggi();
					Thread.sleep(50);
					updateMessage(let.getLetti()+ " su " + let.getSize());
					updateProgress(let.getLetti(), let.getSize());
				}
				return null;
			}
			
		};
		scaricati.textProperty().bind(task.messageProperty());
		barra.progressProperty().bind(task.progressProperty());
		Thread t=new Thread(task);
		t.setName("Download");
		t.setDaemon(true);
		t.start();
		
	}
}
posto il metodo principale della classe LetturaFile, gli altri metodi fanno esattamente quel che dicono:
public void leggi() throws IOException{
		byte[] buffer;
		buffer=dimBuffer();
		in.read(buffer, 0, buffer.length);
		letti+=buffer.length;
		preview.appendText(new String(buffer));
		chiusuraInputStream();
	}
Vi Ringrazio in anticipo per qualsiasi spiegazione, grazie.

6 Risposte

  • Re: Thread e gui javaFX

    Innanzitutto non sono ancora a livelli elevati su JavaFX ma conosco le basi. E le basi sul threading sono relativamente semplici e oltretutto molto simili concettualmente a quello che avviene in Swing.

    In JavaFX c'è un singolo thread dedicato alla interfaccia utente, è chiamato "JavaFX application thread". Essendo unico, NON deve essere tenuto "impegnato" con del proprio codice per troppo tempo. Inoltre siccome la scene graph di JavaFX NON è thread-safe, l'accesso e la modifica della UI va fatta SOLO ed esclusivamente nel contesto del JavaFX application thread.

    Ora: quel tuo 'preview' è una TextArea. Il leggi() lo esegui nel contesto di un thread a parte (che non è il JavaFX application thread) ma lì dentro, attenzione, vai a modificare quella TextArea (quel preview.appendText( ...) ).
    Bene, questo è il punto sbagliato.
  • Re: Thread e gui javaFX

    Ciao, innanzi tutto grazie dei consigli, è tutto oggi che cerco di capire bene come funziona. Sto leggendo molto sul Thread Safe e swing. Ora proverò a scrivere sulla textArea direttamente dal controller e non dal Thread anche se dovrò fare uscire le informazioni comunque dal Thread in qualche modo.
    Ti scriverò nel caso in cui ci riesca!
  • Re: Thread e gui javaFX

    Se si è nel contesto di un thread che non è il JavaFX application thread e si vuole accedere alla UI, si può far "passare" un pezzo di codice nel JavaFX application thread usando il Platform.runLater(Runnable)

    Che è sostanzialmente lo stesso concetto in Swing con il SwingUtilities.invokeLater(Runnable)
  • Re: Thread e gui javaFX

    Allora ho risolto utilizzando un'altra proprietà del task, effettivamente il problema era l'aggiornamento dal task.
    Dal metodo leggi ho fatto uscire direttamente un array di char ed ho cambiato il metodo download così.
    @FXML
    	private void download() throws FileNotFoundException{
    		LetturaFile let=new LetturaFile(new File(percorso.getText()));
    		Task<Void> task=new Task<Void>() {
    
    			@Override
    			protected Void call() throws Exception {
    				String testo="";
    				String temp;
    				while(!let.fineFile()){
    					temp=new String(let.leggi());
    					testo+=temp;
    					Thread.sleep(250);
    					updateMessage(testo);
    					updateTitle(let.getLetti()+" su " + let.getSize());
    					updateProgress(let.getLetti(), let.getSize());
    				}
    				return null;
    			}
    			
    		};
    		preview.textProperty().bind(task.messageProperty());
    		barra.progressProperty().bind(task.progressProperty());
    		scaricati.textProperty().bind(task.titleProperty());
    		Thread t=new Thread(task);
    		t.setName("Download");
    		t.setDaemon(true);
    		t.start();
    		
    	}
    Ti dispiace invece spiegarmi l'utilizzo del Platform.runlater().
    Nel senso che ho capito che serve per passare codice al thread di fx ma se lo utilizzo la gui si blocca.
  • Re: Thread e gui javaFX

    Innanzitutto se il file ha una dimensione abbastanza "consistente", fare

    testo+=temp;

    è altamente inefficiente (dovresti saperlo, in generale).

    Riguardo il runLater è semplice: nel tuo codice iniziale la riga "inappropriata" era l'accesso alla TextArea:

    preview.appendText(new String(buffer));

    Usando runLater, ti bastava fare "passare" nel JavaFX application thread solo questa istruzione, ovvero creando una implementazione di Runnable (tipicamente con una anonymous inner class) il cui run() conteneva appunto questa istruzione. E poi passarla al Platform.runLater
  • Re: Thread e gui javaFX

    Si evidentemente la soluzione di sovrascrivere tutto il testo non era soddisfacente.
    In definitiva il codice migliore dovrebbe essere:
    @FXML
    	private void download() throws FileNotFoundException{
    		LetturaFile let=new LetturaFile(new File(percorso.getText()), preview);
    		Task<Void> task=new Task<Void>() {
    
    			@Override
    			protected Void call() throws Exception {
    				while(!let.fineFile()){
    					let.leggi();
    					Thread.sleep(250);
    					updateMessage(let.getLetti()+ " su " + let.getSize());
    					updateProgress(let.getLetti(), let.getSize());
    				}
    				return null;
    			}
    			
    		};
    		scaricati.textProperty().bind(task.messageProperty());
    		barra.progressProperty().bind(task.progressProperty());
    		Thread t=new Thread(task);
    		t.setName("Download");
    		t.setDaemon(true);
    		t.start();
    		
    	}
    e il codice di leggi
    public void leggi() throws IOException{
    		byte[] buffer;
    		buffer=dimBuffer();
    		in.read(buffer, 0, buffer.length);
    		letti+=buffer.length;
    		Platform.runLater(()->preview.appendText(new String(buffer)));
    		chiusuraInputStream();
    	}
    Ti ringrazio dell'aiuto sei stato gentilissimo.
Devi accedere o registrarti per scrivere nel forum
6 risposte