Fehler aus anderem Thread signalisieren?

Hey, ich bin derzeit mit dem Programmieren einer App beschäftigt.

Unter anderem kommuniziert die App mit einem Server und startet dazu einen neuen Thread damit die GUI nicht laggt.
Jetzt weiß die GUI nie so genau wann die Kommunikation mit dem Server vorbei ist. Deswegen hab ich eine Art Callback-Interface, welches ausgeführt wird wenn die Kommunikation vorbei ist.
Dazu hab ich eine abstrakte Kommunikationsklasse von der mehrere konkrete Kommunikationsklassen erben. Das sieht dann ungefähr so aus:

public abstract class AbstractCommunicationThread implements Runnable {
    protected Callback callback;
     //....

    public abstract void execute();

    @Override
    public void run() {
        execute();
        if (callback != null) {
            callback.execute();
        }
    }
}

Das ganze funktioniert wunderbar solange keine Fehler auftreten. Wenn allerdings ein Fehler auftritt möchte ich das dem GUI-Thread irgendwie mitteilen, sodass der darauf reagieren kann. Gibt es dafür eine elegante Möglichkeit? Ich könnte natürlich zwei Callbacks übergeben, eine für den erfolgreichen Fall und eine für den Fehlerfall, allerdings weiß ich nicht ob das so schön ist.
Eine andere Möglichkeit wäre vllt. das über Flags zu machen, allerdings müsste die GUI dann permanent die Flags pollen um zu gucken ob es einen Fehler gab.

Gibt es da vllt. noch andere elegantere Methoden um das zu lösen?

Grüße

Recht allgemein, die Frage… man könnte sich verschiedene Lösungen vorstellen. Welche davon die geeignetste ist, ist schwer zu beurteilen. Um welche “Fehler” geht es denn? Exceptions? Vermutlich, wenn “execute” ja nichts zurückgibt…

Das über Flags zu lösen wirft mehrere Fragen auf. Erstens erzeugt man damit einen (veränderlichen) Zustand, und das ist bei Multithreading immer eine potentielle Fehlerquelle. Zum anderen: WO sollte das GUI dieses Flag abfragen? Im allgemeinsten Sinne sollte das GUI diesen “AbstractCommunicationThread” ja nicht namentlich kennen - sie sollte also nicht unbedingt irgendwo
abstractCommunicationThread.didSomethingGoWrong(); aufrufen können.

Spontan mal die Möglichkeiten durchgegangen: Man könnte

  • Wie du schon sagtest: Einen normalen Callback und einen ErrorCallback verwenden
  • Den Fehler an den Callback weiterreichen, in dem Sinne, dass er halt eine Methode hat wie void execute(Exception t), der man die Exception übergeben könnte (oder eben “null”, wenn es keine gab… oder meinetwegen eine “Optional”…)
  • Den Fehler an den Callback weiterreichen, in dem Sinne, dass er ZWEI Methoden hat, wie etwa
interface Callback {
    void everythingFineDude();
    void ohDearYouWillHaveToCopeWith(Error error);
}

Ansonsten habe ich beim Durchlesen des geposteten Schnipsels (auch wenn es nur ein Beispiel sein sollte) kurz gezögert. Das ist eine abstrakte Klasse, die eine abstrakte Methode hat, die “analog” zur “run()”-Methode eines Runnables ist - und der Callback scheint ein Interface zu sein, das die gleiche(!) abstrakte Methode hat. Ein paar spontan hingeschriebene Zeilen, NICHT als Empfehlung zu betrachten (dazu sind sie ZU spontan), aber als Pointer in Richtungen, in die man weiterdenken könnte…:

import java.util.Objects;
import java.util.function.Consumer;


public class CallbackTests
{
    public static void main(String[] args)
    {
        testSimple();
        testWithError();
    }
    
    private static void testSimple()
    {
        Runnable communicationRunnable = 
            () -> System.out.println("Executing communication");
        
        Runnable guiCallback =
            () -> System.out.println("GUI was called");
            
        Runnable chained = 
            Runnables.chain(communicationRunnable, guiCallback);
        chained.run();
    }
    
    private static void testWithError()
    {
        Runnable communicationRunnable = 
            () -> System.out.println("Executing communication");
        
        Consumer<Exception> guiCallback =
            e -> System.out.println("GUI was called, error: "+e);
            
            Runnable wrapped = 
                Runnables.wrap(communicationRunnable, guiCallback);
            wrapped.run();
        
        Runnable communicationRunnableWithError = () -> 
        {
            System.out.println("Executing communication with error");
            throw new IndexOutOfBoundsException("Yikes!");
        };
        Runnable wrappedWithError = 
            Runnables.wrap(communicationRunnableWithError, guiCallback);
        wrappedWithError.run();
        
    }
    
}

class Runnables
{
    public static Runnable chain(Runnable r0, Runnable r1)
    {
        Objects.requireNonNull(r0);
        Objects.requireNonNull(r1);
        return () -> { r0.run(); r1.run(); };
    }
    
    public static Runnable wrap(Runnable r0, Consumer<Exception> callback)
    {
        Objects.requireNonNull(r0);
        Objects.requireNonNull(callback);
        return () -> 
        { 
            try
            {
                r0.run();
                callback.accept(null);
            }
            catch (Exception e)
            {
                callback.accept(e);
            }
        };
    }
    
}

(Was passiert, wenn der Callback eine Exception wirft? Naja… ;-))

Wenn du Java 7 + verwenden kannst, dann bietet Callable + Future das: ThreadPoolExecutor + Callable + Future Example - HowToDoInJava

Besten Dank schonmal.
Ja es handelt sich in der Tat um Exceptions. Den Code von Marc muss ich mir morgen nochmal in Ruhe angucken, kann mich abends immer schlecht konzentrieren.
@maki : Hatte auch erst an Future Objekte gedacht allerdings müsste ich da wieder eine Art Polling machen indem ich immer mit isDone() überprüfe ob der Thread fertig ist. Mit der get() Funktion würde ich dann sogar blocken.
Müsste das mal in einem neuen Branch testen wie gut das funktioniert.

@maki Erst hatte ich auch an Callables gedacht, aber … dann dachte ich mir, dass das nicht dabei hilft, 1. einen Fehler an die GUI weiterzureichen und 2. etwas zu machen, wo man im GUI nicht warten muss. (Threading ist halt so eine Sache: Was auch immer in dem Callback gemacht wird: Man müßte noch aufpassen, dass man da NICHT aus einem falschen Thread heraus irgendwas mi dem GUI macht…)

Polling der isDone Methode, erst dann get aufrufen verhindert blockieren, das wuerde ich den GUI Thread machen lassen.

Vielleicht uebersehe ich etwas, aber wie asynchrone Aufrufe machen ohne polling oder blockieren? Einen Tod muss man doch sterben…

Ohja, bei der letzten Antwort hatte ich irgendwie verdrängt, dass das ganze ja auch vom GUI aus gestartet wird.
[ot]
(Zumindest teilweise)
Bei den SwingTasks hatte ich um sowas ja ein bißchen convenience-Infrastruktur drumgewickelt. Die Fehlerbehandlung läuft da über einen java.lang.Thread.UncaughtExceptionHandler, der direkt mit setUncaughtExceptionHandler gesetzt werden kann (und per default ist einer gesetzt, der die Exception in einem Dialog anzeigt). Ein Beispiel sieht man auch in SwingTasks_05_ErrorHandling.java
[/ot]

Dann versenk auch ich mal meine two-cents im Brunnen mit hoffentlich etwas sinnvollem …

1.) Du nutzt das Wort “App”. Daher die wichtige Frage: Desktop-Application oder Android-App ? Hintergrund der Frage: sollte es sich um eine Android-App handeln fallen gewisse “Standard-Ansätze” weg und man muss etwas um die Ecke denken. Wenn es eine normale Desktop-Application ist dann drüfte es so gefühlt um die batzillionen fertiger Codes und Lösungsansätze.

2.) Da ich mich aktuell auch mal wieder mehr mit dem Design statt dem eigentlichen Code beschäftige und mich dafür stark für MVC/MVP interessiere würde ich den Event-basierten Weg nutzen.
Du sagst du hast eine Socket-Verbindung und willst entsprechend auf Rückmeldungen dieser reagieren. Mit java.io kein Thema, denn diese blockiert entweder bis Daten von der Gegenstelle eintreffen oder eine Exception fliegt. Man braucht also nichts pollen. Sieht bei NIO natürlich schon gleich mal ganz anders aus, aber ich muss gestehen: damit hab ich aktuell mal noch so NULL Plan. Bin auch gerade erst dabei mich langsam ranzupirschen.

Was ich beim Event-based way-of-lfe machen würde wäre folgender Ansatz:
Vom GUI-Event erstmal in einen externen Thread auslagern. In der eigentlichen GUI-Event-Methode würde bei mir also erstmal nur dieses Skelett stehen:

{
	new Thread(new Runnable()
	{
		public void run()
		{
			macheWasMitDemEvent(event);
		}
	}).start();
}```
Damit bist du schon mal aus den EDT raus und brauchst dich nicht weiter um ein mögliches freeze der GUI sorgen. So, und egal was da jetzt in dem anderen Thread abläuft verhält sich dieser bei normalen java.io quasi-event-based. Warum? Recht einfach: entweder kommen entsprechende Daten von der Gegenstelle zurück, oder halt eben es wird eine Exception geworfen die man entsprechend fängt und verarbeitet (auf RuntimeException sollte man ggf auch fangen, abhängig ob der Code damit umgehen können soll). Abhängig was dann dabei rauskommt gibt man diese Daten dann an die GUI über wieder-einkoppeln in den EDT weiter:
```public void returnData(final Data data)
{
	java.awt.EventQueue.invokeLater(new Runnable()
	{
		public void run()
		{
			gui.display(data);
		}
	});
}
public void returnError(Exception e)
{
	java.awt.EventQueue.invokeLater(new Runnabled()
	{
		public void run()
		{
			gui.handleError(e);
		}
	});
}```
Man merkt vielleicht die MVP-Lastigkeit des Codes.
Dein View bietet dann entsprechend die API an die Daten entsprechend anzuzeigen oder bei einem Fehler halt mit einem "Popup" darauf hinzuweisen und entsprechend Lösungsmöglichkeiten anzubieten.

Danke für die Antworten.

Es geht um eine Android App, das hätte ich deutlicher schreiben sollen. Der Ansatz sieht interessant aus, vor allem MVC hört sich gut an. Ich muss mich mal ein bisschen einlesen wie das mit dem EDT unter Android genau aussieht.

Ich gehe mal in meiner Naivität davon aus dass es sich bei Android nicht stark von einem “normalen” Desktop-Linux unterscheiden wird. Das OS hat ein entsprechendes Interface welches durch Hardwarekomponenten bedient wird welche via Interrupt dem CPU mitteilen was aktuell anliegt und dieser gibt es über die OS-Queue dann erstmal an die JVM weiter welche selbst dann wiederrum über einen simplen EDT diese Events abarbeitet. Und die Regeln bzgl. dass man halt nicht im EDT arbeitet dürften die gleichen sein. Was ich mir höchstens noch vorstellen kann ist dass es bestimmte anti-lock-Mechanismen gibt die halt verhindern das ein Lock in einer App das gesamte Gerät lahmlegen, was gerade bei heutigen Modellen wo man nicht mehr an das Akku kommt (z.B. die Sony Xperia Serie) nötig wird. Wobei man das natürlich in den Compiler übernehmen könnte dass dieser halt um entsprechende Calls ein passendes Konstrukt baut. Wäre sogar möglich wenn man gewisse Parameter vom API her festlegen würde, wie z.B. dass eine App-Einstiegsmethode (ala main()) nur von bestimmten Threads aus gecallt werden kann/darf (was ja mit regulärem Java nicht garantiert werden kann … ich kann z.B. aus dem EDT auch eine main() callen). Dann könnte man sogar zur Compile-Time prüfen ob man im EDT ist oder nicht und darauf entscheiden ob entsprechende Kopplung nötig wird oder nicht. Aber naja: jeder fängt mal irgendwo an und muss diese Grundregeln der GUI-Entwicklung lernen. Und Systementwickler müssen Techniken finden um drumrum zu kommen wenn es mal wieder irgendjemand vergeigt um halt nicht gleich wie gesagt das Gerät sog. zu “soft-locken”.

[ot]Mein Xperia Z1 hat zwar einen Hardware-Not-Aus-Button, auch wenn gleich schwer erreichbar und nur mit was entsprechend kleinem auszulösen, aber teils find ich es auch von den Designern eine schlechte Idee was komplett ohne Hardware-Buttons umsetzen zu wollen und dann aber durch Verkleben und festes Verlöten im Störfall keine Möglichkeit eines Reset mehr bieten … bin gespannt wann dadurch die nächste Mission-to-Mars drauf geht …
Muss da grad so an Star Trek denken wo ich das so schreibe … was machsten da wenn sich die GUI abschießt aber kein Reset da is … einfach mal mit nem Phaser drauf ?[/ot]

Also bei mir würde das Ganze so aussehen:

  private final Callback callback;
  private Thread runner;

  public AbstractCommunicationThread(Callback callback) {
    if(callback == null) {
      throw new NullpointerException();
    }
    this.callback = callback;
  }

  public syncronized void start() {
    if(runner != null) {
      runner = new Thread(new Runnable() {
        public void run() {
          Throwable t = null;
          try {
            callback.execute();
          } catch(Throwable e) {
            t = e;
          }
          callback.notify(t);
        }
      }
      runner.start();
    }
  }
}```
Und dazu das Interface Callback
```public interface CallBack {
  void notify(Throwable t);
  void execute();
}```
Unverkennbar... wenn bei notify() null übergeben wird, ist alles glatt gelaufen. Ebenso Unverkennbar... Die Übergabe eines Callbacks ist Pflicht.

Wieso rufst du notify auf, auch wenn es keine Exception gibt?

Ich würde als Ergebnis ein Try zurückliefern (siehe etwa try4j, an Scala angelehnt), also eine Art Optional, bei dem im “leeren Fall” auch ein Fehler mitgeliefert werden kann.

Nun, die Aufgabe des Threads wurde irgendwo als Aufgabe aufgerufen. Ich denke mal, der Aufrufer erwartet zumindest eine Rückmeldung, wenn sie erledigt ist und nicht bloß einen Schadensbericht, wenn etwas schief ging. Was der Aufrufer mit einer Null-Fehlermeldung macht, ist erstmal egal, aber zumindest ist eine da.

Natürlich kann man Fehlermeldung und korrekte Ausführung auch in zwei Methoden bringen und das Interface als Adapter vorimplementieren (wenn man nur auf eines reagieren will), ist aber gehupft wie gesprungen, finde ich.