SWT: GUI bei langen Aktionen nicht einfrieren lassen
Länger dauernde Aktionen sollten nie im GUI-Thread laufen. Dies gilt auch für SWT, der GUI-Bibliothek von IBM/Eclipse für Java. Allerdings gibt es bei SWT einige Punkte zu beachten, ohne die ein Umbau schnell sehr mühsam werden kann.
Ausgangslage
Bei den meisten Erklärungen zu SWT wird im SelectionListener eines Knopfes alle Logik eingebaut, die beim Klick darauf ausgeführt werden soll. So lange die daraus resultierenden Aktionen schnell verarbeitet werden können ist dagegen auch nichts einzuwenden.
Dauert es aber länger friert einem sehr schnell das GUI ein. Je nach PC variieren die Auswirkungen von einem flackern der Anzeige bis zu kompletten Blockieren der Anwendung. Der Code wird in so einem Fall wohl meist so aussehen:
Lösung: Threads und asyncExec
Nach einigen Anläufen bin ich bei Threads und einer Synchronisierung des GUI über asyncExec
gelandet. Die lange dauernden Aktionen werden in einen eigenen Thread ausgelagert und im SelectionListener nur noch gestartet. Die Verarbeitung erfolgt so losgelöst vom GUI-Thread und behindert das Neuzeichnen der Oberfläche nicht – dies genügt damit die Anwendung viel reaktiver erscheint.
Da man nun während der Ausführung der Aktion weiterarbeiten kann, steht man unter Umständen vor neuen Problemen. Falls die gleiche Aktion nicht noch einmal parallel dazu gestartet werden darf, muss man dies nun explizit verhindern. Je nach Anwendung genügt es den entsprechenden Knopf beim Start des Arbeitsthreads zu deaktivieren und erst beim beenden wieder zu aktivieren.
Eine Implementierung mit einer eigenen Thread-Klasse kann so aussehen:
Der Knopf über den die zeitintensive Aktion gestartet wird ist wie alle anderen GUI-Elemente in SWT aber nicht direkt aus einem anderen Thread heraus veränderbar. Damit der Ausführungskontext stimmt müssen alle Veränderungen dieser Elemente als Runnable der Methode asyncExec
übergeben werden. Wichtig ist das man das Display-Objekt nutzt mit dem man die Shell der Anwendung initialisiert hat.
Versucht man asyncExec zu umgehen wird SWT mit dieser Exception antworten:
Exception in thread "Thread-0" org.eclipse.swt.SWTException: Invalid thread access
Sind alle Zugriffe entsprechend umgeformt, kann man im SelectionListener des Knopfes die Thread-Klasse starten:
Fazit
Mit dieser Umbauarbeit kann man auch lange laufende Aktionen ausführen ohne dass einem das GUI einfriert oder an Reaktionsfähigkeit einbüsst. Dieser Ansatz ist ein wenig aufwändig, erfüllt aber seinen Zweck. (Das ganze Beispiel ist auf Github verfügbar)
Falls es einfachere Wege gibt würde ich mich über einen Kommentar freuen.