Java Getriebe

Java und NetBeans

Hintergrundprozesse ohne den EDT zu verlassen.

Ich hatte vor längerer Zeit schon mal den SecondaryLoop aus Java7 erwähnt und mich das selber fast genauso lange nicht mehr mit ihm beschäftigt. Jetzt bot sich allerdings eine Gelegenheit das nachzuholen. Ein kleiner „Downloader“, der eine ausgewählte Datei auf den lokalen Rechner kopieren sollte.

Der SecondaryLoop funktioniert ganz grob folgendermaßen.

  1. Über das AWT-Toolkit holt man sich die aktuelle EventQueue
  2. Diese Queue kann dann eine SecondaryLoop Instanz erzeugen.
  3. Man erzeugt sich einen Thread und übergibt ihm ein Runnable, welches die nebenläufigen Arbeiten erledigen soll.
  4. Anschließend wird der Thread gestartet und loop.enter(); aufgerufen
  5. Innerhalb des Runnables muss unbedingt ein loop.exit(); aufgerufen werden um die Verarbeitung nach loop.enter(); fortführen zu können.

Wohl gemerkt spielt sich das alles (außer dem letzten Punkt) innerhalb des „Event Dispatch Thread“ (EDT) ab. Vor allem ist interessant, dass ich nach dem Aufruf von loop.enter(); weiteren Sourcecode in der gleichen Methoden schreiben kann, welcher auf die Ergebnisse des nebenläufigen Threads zugreift. Ohne SecondaryLoop müsste ich den Thread starten, welcher dann am Ende wieder ein Event im EDT auslösen muss. Die Ereignisse stehen dann nicht selten an vollkommen unterschiedlichen Stellen im Programmcode und machen eine Fehlersuche schwerer oder eine einfache Wartung deutlich unübersichtlicher.

Um die Sache noch einfacher für mich zu machen habe ich eine kleine Klasse BackgroundRunner geschrieben, die mir das Handling rund um den SecondaryLoop vollständig abnimmt. Der BackgroundRunner bekommt von mir nur noch ein Callable für den Hintergrundprozess übergeben. Dadurch bekomme ich als Nebeneffekt einen Rückgabewert vom Hintergrundprozess und auch noch ein „praktikables“ Exceptionhandling geschenkt. Der Aufruf in meinem actionPerformed beschränkt sich dann auch sehr wenige Zeilen:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void actionPerformed(ActionEvent e) {
  // some stuff
  FileObject localFile;
  try {
    localFile = BackgroundRunner.postLooped(
          new Downloader(localPath, remoteInfo));
  } catch(Exception ex) {
    //ExceptionHandling, Fehlermeldungen
    return;
  }
  // do something with localFile within EDT
}

Der Downloader ist mein Callable und läd die Datei aus remoteInfo herunter und speichert ihn in localPath. Heraus kommt der Dateiname der lokalen Datei, welche dann nach belieben weiter verarbeitet werden kann. Und das alles ohne actionPerformed irgendwie zu verlassen.

Die Klasse BackgroundRunner

Die Klasse selbst ist ein Runnable und verwendet Generics um den Rückgabetyp zu definieren:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public final class BackgroundRunner<R>
    implements Runnable {
  private final Callable<R> delegate;
  private final SecondaryLoop loop;
  private R result;
  private Exception thrownException;

  private BackgroundRunner(
      Callable<R> delegate, SecondaryLoop loop) {
    this.delegate = delegate;
    this.loop = loop;
  }

  public static <R> R postLooped(
      Callable<R> backgroundRun)
      throws Exception
  {
    //more later
  }

  @Override
  public void run() {
    try {
      result = delegate.call();
    } catch(Exception ex) {
      result = null;
      thrownException = ex;
    } finally {
      loop.exit();
    }
  }
}

Die einzige Schnittstelle nach außen ist die Methode postLooped dem das Callable<R> übergeben wird und den entsprechenden Rückgabewert liefert. Das throws Exception kommt vom Callable Interface und kann leider nicht weiter eingegrenzt werden. Dem Konstruktor als solchen ist denke ich nichts weiter hinzuzufügen.

Die run-Methode beschränkt sich im wesentlichen darauf das Callable aufzurufen und dessen Ausgaben (Rückgabe und Exception) zu speichern. Außerdem steht hier das wichtige loop.exit(); im finally Block um sicherzustellen dass der Loop auf wirklich beendet wird.

Der wirklich interessante Teil der Klasse passiert eigentlich in der statischen Methode `postLooped`:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  public static <R> R postLooped(
      Callable<R> backgroundRun)
      throws Exception {
    // Punkt 1
    EventQueue eventQueue = Toolkit.getDefaultToolkit()
        .getSystemEventQueue();
    // Punkt 2
    SecondaryLoop loop =
        eventQueue.createSecondaryLoop();

    // Punkt 3
    BackgroundRunner<R> runner =
        new BackgroundRunner<>(backgroundRun, loop);
    Thread thread = new Thread(runner,
        BackgroundRunner.class.getSimpleName() + "-"
        + backgroundRun.getClass().getSimpleName());

    // Punkt 4
    thread.start();
    loop.enter();

    // post loop stuff
    if(runner.thrownException != null) {
      throw runner.thrownException;
    }
    return runner.result;
  }

Ich habe die Punkte vom Anfang diese Blogartikels mal als Kommentare in die Methode gesetzt. So sollte direkt ersichtlich werden, was da gerade passiert. Erwähnenswert ist vielleicht noch der „post loop stuff“ bei dem ich eine eventuell aufgetretene Exception (siehe `run`-Methode) einfach weiter reiche.

Mit dieser Klasse lassen sich Nebenläufigkeiten um einiges leichter Implementieren und warten als mit einem SwingWorker oder sonstigen Threadpools.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.