Librerie grafiche SWT : operare sui widgets da Threads secondari

Recentemente sto lavorando ad un rcp, mi è capitato quindi di dover apportare delle modifiche in runtime sullo stato dei widgets grafici del mio prodotto.
Spesso può succedere che deleghiamo ad un thread secondario delle operazioni che richiedono un particolare tempo d’attesa,nel mio caso ad esempio il thread resta in ascolto su uno stream di un processo in esecuzione.
Avendo ricevuto un segnale,il thread ad esempio potrebbe avere la necessità di apportare una modifica ad un widgets,sempre nel mio caso una volta ricevuta una stringa dallo stream il thread vuole stamparla su un TextViewer e ritornare in ascolto per una nuova stringa.
In questo contesto è bene tenere presente però, che il thread secondario non ha un privilegio d’accesso tale da poter apportare direttamente modifiche sul display,ed è anche ragionevole tale comportamento perchè gli eventi grafici devono essere schedulati secondo un ordine ben preciso da un apposito thread,non vengono elaborati per come arrivano,a casaccio.

Le SWT ci offrono due strumenti utile per poter effettuare queste operazioni in modo pulito: i metodi
asyncExec(Runnable) e syncExec(Runnable).
I due metodi hanno come argomento un oggetto di tipo runnable,possiamo istanziarvi una classe che implementa il metodo run() con le operazioni grafiche che ci interessano.
es.


 display.asyncExec(new Runnable() {
					public void run() {
			if(StatusLineContribution.isBuildEnabled())
				StatusLineContribution.setBuildEnabled(false);
				StreamView.resetStreamViewer(textDoc,streamViewer);
					}
			  });


Sopra non faccio altro che disabilitare il bottone Build,e resettare un Viewer,tutto dentro il metodo run().
La differenza tra syncExec e asyncExec è molto importante.
Con un’invocazione di syncExec il thread secondario ritorna in uno stato ready,solo se è l’evento emesso è stato portato a termine;con asyncExec quest’attesa non c’è e il thread secondario riprende subito il controllo.
Nel mio lavoro ad esempio ho dovuto usare syncExec nel ciclo di lettura dallo stream e stampa sul Viewer


	try {
					while((text=br.readLine())!= null)
						display.syncExec(new Runnable() {
							public void run() {
							StreamView.appendToStreamViewer(stream,text,textDoc,streamViewer);
							}//inner run
					}
			      );//syncExec
				}

Questo è necessario,pensiamo infatti cosa succederebbe se invocassi asyncExec: una stringa sopraggiungerebbe dallo stream readerbr,verrebbe copiata in text,verrebbe immesso in schedulazione l’eventoStreamView.appendToStreamViewer(stream,text,textDoc,streamViewer);.
Nel frattempo prima ancora che l’evento precedente abbia subito il dispatch,un’altra stringa potrebbe sopraggiungere dallo stream reader,e un altro evento di stampa potrebbe essere schedulato,avendo possibilmente una priorità maggiore del precedente,e le due stringhe potrebbero ad esempio essere stampate in ordine inverso.
Non potendo fare ipotesi su come vengono schedulati gli eventi grafici,è meglio usare un metodo syncExec,la ricezione della stringa e la sua stampa procedono in modo sincrono e il comportamento risultante è quello che ci aspettiamo.

Riporto per completezza il thread che uso per redirigere lo stream del processo in output sul Viewer grafico.


class ServerStreamRedirect extends Thread {
		  private Display display;
		  private Process process;
		  final int stream;
		  Document textDoc;
		  TextViewer streamViewer;
		  String text="";

		  public ServerStreamRedirect(Display display,Process process,final int stream,Document textDoc,TextViewer streamViewer) {
		    this.display = display;
		    this.process=process;
		    this.stream=stream;
		    this.textDoc=textDoc;
		    this.streamViewer=streamViewer;

		  }

		  public void run()
		  {

			  display.asyncExec(new Runnable() {
					public void run() {
			if(StatusLineContribution.isBuildEnabled())
				StatusLineContribution.setBuildEnabled(false);
				StreamView.resetStreamViewer(textDoc,streamViewer);
					}
			  });

				InputStream in;

				switch(stream)
				{
				case StreamView.STD_OUTPUT:
					in = process.getInputStream();
				    break;
				default:
					in=process.getErrorStream();
					break;
				}

				InputStreamReader isr = new InputStreamReader(in);
				BufferedReader br= new BufferedReader(isr);

				try {
					while((text=br.readLine())!= null)
						display.syncExec(new Runnable() {
							public void run() {
							StreamView.appendToStreamViewer(stream,text,textDoc,streamViewer);
							}//inner run
					}
			      );//syncExec
				}
				catch(IOException e)
				{
					e.printStackTrace();
				}

				try { process.waitFor(); } // Wait for process to terminate  and catch any Exceptions.
		        catch (InterruptedException e)
		         {System.err.println("Process was interrupted"); }

		        try {
					br.close();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} // Done.
				  display.asyncExec(new Runnable() {
						public void run() {
				if(! StatusLineContribution.isBuildEnabled())
					StatusLineContribution.setBuildEnabled(true);
						}
				  });

		  }

6 Risposte a “Librerie grafiche SWT : operare sui widgets da Threads secondari”

  1. Leandro Dice:

    Molto interessante, ottima guida grazie mille!
    solo una curiosità…quando istanzio la mia classe che gestirà il thread secondario devo passargli per forza al costruttore un istanza di Display? e in caso affermativo come lo definisco Display sulla classe di partenza?

  2. Ordeal Dice:

    Affinchè il thread secondario possa operare su dei widgets istanziati altrove,lo stesso Display di tali widgets deve essere passato come parametro al costruttore del thread secondario.

    Per istanziare un Display,bisogna usare la classe Display da org.eclipse.swt.widgets.Display;
    Semmai può esserti molto utile ricavarlo dai widgets istanziati: esiste a tal proposito il metodo getDisplay(), di tutti i componenti swt,ad esempio nel classico metodo della vista
    public void createPartControl(Composite parent) ;
    ottengo subito il display dal parent :
    display=parent.getDisplay();
    allo stesso modo lo puoi applicare a tutti i widgets di swt.

  3. dandan86 Dice:

    bell’articolo uno dei pochi in italiano che trattano tale argomento!
    però nei miei esperimenti ho problemi di dead-lock con la grafica, ovvero se i thread secondari tentano di modificare qualcosa nella finestra grafica, la finestra risulta non modificabile con il mouse(quindi bloccata) e a seguito di ciò devo chiudere il programma con il tasck-manager.
    mi rendo conto che non è un forum però ormai l’ho detto….

  4. Ordeal Dice:

    @dandan86,posta il codice come esempio

  5. Ordeal’s Movie Manager RCP(Part3) « Ordeal’s Weblog Dice:

    [...] meccanismo usato nel ciclo while, è lo stesso di cui parlavo qui Il metodo run() dell’interfaccia Runnable() che stiamo implementando è invocato direttamente [...]

  6. Faulgesseflus Dice:

    Hello. It is test.

Lascia una Risposta