Die vorliegende Übersetzung wurde maschinell erstellt. Im Falle eines Konflikts oder eines Widerspruchs zwischen dieser übersetzten Fassung und der englischen Fassung (einschließlich infolge von Verzögerungen bei der Übersetzung) ist die englische Fassung maßgeblich.
Fehlerbehandlung
Das Konstrukt try
/catch
/finally
in Java vereinfacht die Fehlerbehandlung und wird sehr häufig eingesetzt. Es ermöglicht die Verknüpfung von Fehler-Handlern mit einem Codeblock. Dies geschieht intern durch die Anhäufung von Metadaten zu den Fehler-Handlern auf dem Aufruf-Stack. Wird eine Ausnahme ausgelöst, sucht die Laufzeit beim Aufruf-Stack nach einem zugehörigen Fehler-Handler und ruft diesen auf. Wird kein passender gefunden, wird die Ausnahme an die Aufruf-Kette weitergegeben.
Dies funktioniert gut bei synchronem Code. Die Fehlerbehandlung in asynchronen und verteilten Programmen stellt jedoch einige Herausforderungen dar. Da ein asynchroner Aufruf sofort zurückgegeben wird, befindet sich der Aufrufer nicht auf dem Aufruf-Stack, wenn der asynchrone Code ausgeführt wird. Das bedeutet, dass nicht behandelte Ausnahmen in einem asynchronen Code vom Aufrufer nicht in der üblichen Weise behandelt werden können. In der Regel werden Ausnahmen, die in einem asynchronen Code auftreten, behandelt, indem der Fehlerstatus an ein Callback übergeben wird, das an die asynchrone Methode übermittelt wird. Alternativ erfolgt bei Verwendung von Future<?>
die Meldung eines Fehlers, wenn Sie versuchen, darauf zuzugreifen. Dies ist keineswegs ideal, da dem Code, der die Ausnahme empfängt (das Callback oder den Code, das bzw. der Future<?>
verwendet), der Kontext des ursprünglichen Aufrufs fehlt und er die Ausnahme möglicherweise nicht adäquat behandeln kann. Darüber hinaus kann es bei einem verteilten asynchronen System, bei dem mehrere Komponenten parallel ausgeführt werden, gleichzeitig zu mehreren Fehlern kommen. Dabei kann es sich um unterschiedliche Fehlertypen von unterschiedlichem Schweregrad handeln, die alle entsprechend behandelt werden müssen.
Das Bereinigen einer Ressource nach einem asynchronen Aufruf ist ebenfalls schwierig. Im Gegensatz zum synchronen Code können Sie für das Bereinigen von Ressourcen nicht das Konstrukt "try/catch/finally" im aufrufenden Code verwenden, da die Vorgänge, die im Try-Block initiiert wurden, möglicherweise noch weiter fortgeführt werden, wenn der Finally-Block ausgeführt wird.
Das Framework stellt einen Mechanismus bereit, durch den die Fehlerbehandlung bei einem verteilten asynchronen Code ähnlich einfach wird wie die Java-Fehlerbehandlung in Form von "try/catch/finally".
ImageProcessingActivitiesClient activitiesClient = new ImageProcessingActivitiesClientImpl(); public void createThumbnail(final String webPageUrl) { new TryCatchFinally() { @Override protected void doTry() throws Throwable { List<String> images = getImageUrls(webPageUrl); for (String image: images) { Promise<String> localImage = activitiesClient.downloadImage(image); Promise<String> thumbnailFile = activitiesClient.createThumbnail(localImage); activitiesClient.uploadImage(thumbnailFile); } } @Override protected void doCatch(Throwable e) throws Throwable { // Handle exception and rethrow failures LoggingActivitiesClient logClient = new LoggingActivitiesClientImpl(); logClient.reportError(e); throw new RuntimeException("Failed to process images", e); } @Override protected void doFinally() throws Throwable { activitiesClient.cleanUp(); } }; }
Die TryCatchFinally
-Klasse und deren Varianten TryFinally
und TryCatch
funktionieren ähnlich wie Javas try
/catch
/finally
. Mit dieser Lösung können Sie Ausnahme-Handler mit Blöcken von Workflow-Code verknüpfen, die als asynchrone und Remote-Aufgaben ausgeführt werden können. Die doTry()
-Methode entspricht logisch dem try
-Block. Das Framework führt den Code automatisch in doTry()
aus. Eine Liste von Promise
-Objekten kann an den Konstruktor von TryCatchFinally
übergeben werden. Die doTry
-Methode wird ausgeführt, wenn alle Promise
-Objekte, die an den Konstruktor übergeben wurden, bereit sind. Wird eine Ausnahme von einem Code ausgelöst, der asynchron innerhalb von doTry()
aufgerufen wurde, werden alle Vorgänge in doTry()
abgebrochen und doCatch()
aufgerufen, um die Ausnahme zu behandeln. Wenn beispielsweise in der obigen Auflistung downloadImage
eine Ausnahme auslöst, dann werden createThumbnail
und uploadImage
abgebrochen. Wenn alle asynchronen Vorgänge beendet wurden (abgeschlossen, fehlgeschlagen oder abgebrochen), wird abschließend doFinally()
aufgerufen. Es kann zum Bereinigen von Ressourcen verwendet werden. Sie können diese Klassen auch gemäß Ihren Anforderungen verschachteln.
Wenn eine Ausnahme in doCatch()
gemeldet wird, stellt das Framework einen vollständigen logischen Aufruf-Stack mit asynchronen und Remote-Aufrufen bereit. Dies kann beim Debuggen nützlich sein, insbesondere bei asynchronen Methoden, die andere asynchrone Methoden aufrufen. Eine Ausnahme von downloadImage führt beispielsweise zu einer Ausnahme wie der folgenden:
RuntimeException: error downloading image at downloadImage(Main.java:35) at ---continuation---.(repeated:1) at errorHandlingAsync$1.doTry(Main.java:24) at ---continuation---.(repeated:1) …
TryCatchFinally-Semantik
Die Hinrichtung einesAWS Flow Frameworkfür Java-Programm kann als Baumstruktur gleichzeitig ausgeführter Verzweigungen visualisiert werden. Durch den Aufruf einer asynchronen Methode, einer Aktivität oder TryCatchFinally
wird eine neue Verzweigung in dieser Baumstruktur der Ausführung angelegt. Der Bildverarbeitungs-Workflow beispielsweise ist in Form einer Baumstruktur auf folgender Abbildung zu sehen.
Ein Fehler in einer Verzweigung der Ausführung führt zu einer Entladung der Verzweigung, genau wie eine Ausnahme die Entladung eines Aufruf-Stacks in einem Java-Programm verursacht. Dieser Vorgang setzt sich fort, bis entweder der Fehler behandelt oder der Stamm erreicht ist. In diesem Fall wird die Workflow-Ausführung beendet.
Das Framework meldet Fehler, die bei der Verarbeitung von Aufgaben auftreten, als Ausnahmen. Es verknüpft die Ausnahme-Handler (doCatch()
-Methoden), die in TryCatchFinally
definiert sind, mit allen Aufgaben, die vom Code im entsprechenden doTry()
erstellt wurden. Schlägt eine Aufgabe fehl - beispielsweise aufgrund einer Zeitüberschreitung oder einer nicht behandelten Ausnahme -, wird die entsprechende Ausnahme ausgelöst und die zugehörigedoCatch()
wird aufgerufen, um damit umzugehen. Damit dies erreicht wird, arbeitet das Framework mit Amazon SWF zusammen, um Remote-Fehler weitergeben und diese als Ausnahmen im Kontext des Aufrufers wieder aufleben zu lassen.
Abbruch
Tritt eine Ausnahme im asynchronen Code auf, springt das Steuerelement direkt zum catch
-Block und überspringt den verbleibenden Code im try
-Block. z. B.:
try { a(); b(); c(); } catch (Exception e) { e.printStackTrace(); }
Bei diesem Code wird, wenn b()
eine Ausnahme auslöst, c()
niemals aufgerufen. Vergleichen Sie dies mit einem Workflow:
new TryCatch() { @Override protected void doTry() throws Throwable { activityA(); activityB(); activityC(); } @Override protected void doCatch(Throwable e) throws Throwable { e.printStackTrace(); } };
Hier werden Aufrufe von activityA
, activityB
und activityC
erfolgreich zurückgegeben und führen zur Erstellung dreier Aufgaben, die asynchron ausgeführt werden. Angenommen, die Aufgabe für activityB
verursacht zu einem späteren Zeitpunkt einen Fehler. Der Fehler wird von Amazon SWF im Verlauf protokolliert. Aus diesem Grund versucht das Framework zunächst alle anderen Aufgaben abzubrechen, die aus dem Bereich desselben doTry()
stammen. In diesem Fall sind das activityA
und activityC
. Nach Beendigung aller Aufgaben (durch Abbrechen, Fehlschlagen oder erfolgreichem Abschließen), wird die entsprechende doCatch()
-Methode aufgerufen, um den Fehler zu behandeln.
Im Gegensatz zum synchronen Beispiel, bei dem c()
niemals ausgeführt wurde, wurde activityC
hier aufgerufen. Zudem wurde eine Aufgabe für die Ausführung eingeplant. Deshalb versucht das Framework einen Abbruch, für dessen Erfolg es aber keine Garantie gibt. Der Abbruch kann nicht garantiert werden, da die Aktivität möglicherweise bereits abgeschlossen ist, die Abbruchanforderung ignoriert oder fehlschlägt. Das Framework garantiert jedoch, dass doCatch()
nur aufgerufen wird, wenn alle Aufgaben, die über das entsprechende doTry()
gestartet wurden, abgeschlossen sind. Es garantiert zudem, dass doFinally()
nur aufgerufen wird, wenn alle Aufgaben, die vom doTry()
- und doCatch()
-Block gestartet wurden, abgeschlossen sind. Wenn beispielsweise die Aktivitäten im obigen Beispiel voneinander abhängen, sagen SieactivityB
hängt vonactivityA
undactivityC
aufactivityB
, dann die Stornierung vonactivityC
wird sofort sein, da es erst in Amazon SWF geplant istactivityB
Schließt ab:
new TryCatch() { @Override protected void doTry() throws Throwable { Promise<Void> a = activityA(); Promise<Void> b = activityB(a); activityC(b); } @Override protected void doCatch(Throwable e) throws Throwable { e.printStackTrace(); } };
Aktivitäts-Heartbeat
DieAWS Flow FrameworkDer kooperative Abbruchmechanismus von Java ermöglicht den ordnungsgemäßen Abbruch übertragener Aktivitätsaufgaben. Wird der Abbruch ausgelöst, werden blockierte Aufgaben oder Aufgaben, die darauf warten, zu einem Worker zugewiesen zu werden, automatisch abgebrochen. Wenn eine Aufgabe aber bereits einem Worker zugewiesen wurde, fordert das Framework den Abbruch der Aktivität an. Ihre Aktivitätsimplementierung muss diese Abbruchanforderungen explizit behandeln können. Dies geschieht durch das Übermitteln von Heartbeats Ihrer Aktivität.
Durch das Senden von Heartbeats ist die Aktivitätsimplementierung in der Lage, den Fortschritt einer andauernden Aufgabe zu melden. Dies unterstützt die Überwachung und ermöglicht der Aktivität zu prüfen, ob Abbruchanforderungen vorliegen. Die recordActivityHeartbeat
-Methode löst bei Anforderung eines Abbruchs eine CancellationException
aus. Die Aktivitätsimplementierung kann diese Ausnahme abfangen und auf die Abbruchanforderung reagieren oder die Anforderung durch "Verschlucken" der Ausnahme ignorieren. Um der Abbruchanforderung Rechnung zu tragen, sollte die Aktivität die gewünschte Bereinigung vornehmen, sofern erforderlich, und dann CancellationException
erneut auslösen. Wird diese Ausnahme von einer Aktivitätsimplementierung ausgelöst, erfasst das Framework, dass die Aktivitätsaufgabe im abgebrochenen Status beendet wurde.
Das folgende Beispiel zeigt eine Aufgabe, bei der Bilder heruntergeladen und verarbeitet werden. Es kommt nach jeder Verarbeitung eines Bilds zu einem Heartbeat. Wird ein Abbruch gefordert, wird bereinigt und die Ausnahme zur Bestätigung des Abbruchs erneut ausgelöst.
@Override public void processImages(List<String> urls) { int imageCounter = 0; for (String url: urls) { imageCounter++; Image image = download(url); process(image); try { ActivityExecutionContext context = contextProvider.getActivityExecutionContext(); context.recordActivityHeartbeat(Integer.toString(imageCounter)); } catch(CancellationException ex) { cleanDownloadFolder(); throw ex; } } }
Das Senden von Aktivitäts-Heartbeats ist nicht erforderlich, wird aber empfohlen, wenn die Ausführung der Aktivität lange dauert oder dabei kostenintensive Operationen ausgeführt werden, die im Falle eines Fehlers abgebrochen werden sollten. Sie sollten heartbeatActivityTask
periodisch von der Aktivitätsimplementierung aus aufrufen.
Kommt es bei der Ausführung der Aktivität zu einer Zeitüberschreitung, wird die ActivityTaskTimedOutException
ausgelöst und getDetails
auf dem Ausnahmeobjekt gibt für die entsprechende Aktivitätsaufgabe die Daten zurück, die an den letzten erfolgreichen Aufruf von heartbeatActivityTask
übergeben wurden. Die Workflow-Implementierung kann anhand dieser Informationen feststellen, wie weit die Ausführung fortgeschritten war, ehe es zu einer Zeitüberschreitung bei der Aktivitätsaufgabe kam.
Anmerkung
Zu viele Heartbeats werden nicht angezeigt, da Amazon SWF möglicherweise Heartbeat-Anforderungen drosselt. Sehen Sie dasAmazon Simple Workflow Service Entwicklerhandbuchfür Limits von Amazon SWF.
Explizites Abbrechen einer Aufgabe
Abgesehen von Fehlerbedingungen gibt es noch andere Fälle, in denen eine Aufgabe explizit abzubrechen ist. So muss beispielsweise eine Aktivität zur Verarbeitung von Zahlungen mit der Kreditkarte abgebrochen werden, wenn der Benutzer den Auftrag storniert. Das Framework ermöglicht das explizite Abbrechen von Aufgaben, die mit TryCatchFinally
erstellt wurden. Im folgenden Beispiel wird die Zahlungsaufgabe abgebrochen, wenn während der Verarbeitung der Zahlung ein Signal empfangen wird.
public class OrderProcessorImpl implements OrderProcessor { private PaymentProcessorClientFactory factory = new PaymentProcessorClientFactoryImpl(); boolean processingPayment = false; private TryCatchFinally paymentTask = null; @Override public void processOrder(int orderId, final float amount) { paymentTask = new TryCatchFinally() { @Override protected void doTry() throws Throwable { processingPayment = true; PaymentProcessorClient paymentClient = factory.getClient(); paymentClient.processPayment(amount); } @Override protected void doCatch(Throwable e) throws Throwable { if (e instanceof CancellationException) { paymentClient.log("Payment canceled."); } else { throw e; } } @Override protected void doFinally() throws Throwable { processingPayment = false; } }; } @Override public void cancelPayment() { if (processingPayment) { paymentTask.cancel(null); } } }
Empfangen von Benachrichtigungen über abgebrochene Aufgaben
Wird eine Aufgabe im abgebrochenen Status beendet, informiert das Framework die Workflow-Logik durch Auslösen einer CancellationException
. Wenn eine Aktivität im abgebrochenen Status beendet wird, wird ein Datensatz zum Verlauf hinzugefügt und das Framework ruft das erforderliche doCatch()
mit einer CancellationException
auf. Wie im vorherigen Beispiel gezeigt, empfängt der Workflow eine CancellationException
, wenn die Aufgabe der Zahlungsverarbeitung abgebrochen wird.
Eine unbehandelte CancellationException
wird wie jede andere Ausnahme in der Ausnahmeverzweigung weiter nach oben gereicht. Die doCatch()
-Methode empfängt die CancellationException
aber nur, wenn es im Scope keine weitere Ausnahme gibt. Andere Ausnahmen werden höher priorisiert als der Abbruch.
Verschachteltes TryCatchFinally
Sie können TryCatchFinally
gemäß Ihren Anforderungen verschachteln. Da jedes TryCatchFinally
eine neue Verzweigung in der Baumstruktur der Ausführung erstellt, können Sie verschachtelte Bereiche anlegen. Ausnahmen im übergeordneten Scope führen zu Abbruchversuchen bei allen Aufgaben, die durch ein verschachteltes TryCatchFinally
' in ihnen initiiert wurden. Allerdings werden Ausnahmen in einem verschachtelten TryCatchFinally
nicht automatisch an das übergeordnete Element weitergegeben. Wenn Sie eine Ausnahme aus einem verschachtelten TryCatchFinally
an das enthaltene TryCatchFinally
weitergeben möchten, sollten Sie die Ausnahme in doCatch()
erneut auslösen. Anderes ausgedrückt: Nur unbehandelte Ausnahmen steigen wie Javas try
/catch
-Konstrukt auf. Wenn Sie ein verschachteltes TryCatchFinally
durch Aufruf der Abbruchmethode abbrechen, wird das verschachtelte TryCatchFinally
abgebrochen, aber nicht automatisch auch das enthaltene TryCatchFinally
.
new TryCatch() { @Override protected void doTry() throws Throwable { activityA(); new TryCatch() { @Override protected void doTry() throws Throwable { activityB(); } @Override protected void doCatch(Throwable e) throws Throwable { reportError(e); } }; activityC(); } @Override protected void doCatch(Throwable e) throws Throwable { reportError(e); } };