Am 14. September 2017 haben wir eine überarbeitete Fassung unserer Datenschutzrichtlinie veröffentlicht. Wenn Sie video2brain.com weiterhin nutzen, erklären Sie sich mit diesem überarbeiteten Dokument einverstanden. Bitte lesen Sie es deshalb sorgfältig durch.

Scala Grundkurs

Future

LinkedIn Learning kostenlos und unverbindlich testen!

Jetzt testen Alle Abonnements anzeigen
Bei einem Future geht es darum, mit Werten zu arbeiten, die erst in der Zukunft zur Verfügung stehen. Beispielsweise wird eine Berechnung auf einem anderen Thread durchgeführt oder über das Netzwerk wird auf eine bestimmte Ressource zugegriffen.

Transkript

"Future" ist ein Mechanismus, den man beispielsweise auch aus Programmiersprachen, wie Java kennt. Er dient dazu, um beispielsweise einen Ausdruck darüber zu geben, dass es einen Wert gibt, der erst in Zukunft zur Verfügung stehen wird. Die Ursache dafür kann beispielsweise sein, dass eine Berechnung auf einem anderen Thread durchgeführt wird oder dass es einen Netzwerkzugriff auf eine bestimmte Ressource gibt. Nichtsdestotrotz erlaubt es ein Future mit einem solchen Wert, insofern bereits zu arbeiten, als dass man ihn innerhalb des Codes verwenden kann und in der Zwischenzeit andere Dinge ausführen kann. Selbstverständlich sind Futures innerhalb von Scala etwas mächtiger, als sie es beispielsweise in Java sind. Und ich zeige Ihnen nun, wie Sie beispielsweise ein solches Future in eine Anwendung einführen können. Ich befinde mich nun in einer Applikation, die eine Payment API hat. Diese Payment API wird dafür genutzt, um beispielsweise bei einem Server abzufragen, ob eine Transaktion okay ist oder nicht. Aktuell handelt es sich hierbei um einen blockierenden Aufruf. Das heißt, jedes Mal, wenn die "postToUrl" aufgerufen wird, wird eine Verbindung initialisiert und es wird gewartet, dass der Inhalt der Verbindung zurückgeliefert wird. Der Inhalt ist dann, wie in diesem Fall, der Inhalt der Website. Das Ganze möchte ich nun mithilfe eines Future soweit verlagern, als dass ich nicht darauf warten möchte, dass der Inhalt der Website bereits heruntergeladen wurde. Nichts leichter als das. Ich verwende also den Aufruf von "Future" aus dem Package "scala.concurrent". Future ist ähnlich wie eine Try-Funktion so zu nutzen, dass man einen eigenen Funktionsblock definiert für das, was innerhalb des Futures ablaufen soll. Die Besonderheit ist nun also, dass ein Objekt entsteht, das ein Future darstellt vom Typ "String" in diesem Fall, denn im Normalfall liefert dieser Aufruf also einen String zurück. In dem Fall ist es also ein "Future" in der Art und Weise, das einen String beinhaltet. Wenn ich nun versuchen würde meine Anwendung einfach zu kompilieren, werde ich eine Fehlermeldung erhalten. Die Fehlermeldung, die hier für mich relevant ist, ist, dass es keinen "ExecutionContext" gibt für diesen Aufruf des Future. Scala kennt hier einen besonderen Mechanismus für den ExecutionContext. Dieser wird über einen impliziten Parameter übergeben. Alles, was innerhalb eines solchen Futures ausgeführt wird, wird auf einem Thread-Pool ausgeführt. Dieser Thread-Pool wird abstrahiert von einem sogenannten "ExecutionContext". Um einen solchen ExecutionContext also zu definieren, verlange ich hier einen impliziten Parameter, mit der Bezeichnung "ExecutionContext". Was ich also in dem Moment tue, ist die Verantwortung für die Definition eines ExecutionContext an den Aufrufer der Funktion "postToUrl" zu übergeben. Jetzt muss also nun jeder Aufrufer von "postToUrl" einen solchen ExecutionContext definieren oder ich definiere diesen ExecutionContext innerhalb des Kontextes "PaymentApi". Das werde ich nun erst einmal für Probezwecke tun, indem ich definiere: "implicit val executionContext =". In dem Fall gibt es einen sogenannten "globalen" ExecutionContext. Diesen kann ich verwenden, denn er wird standardmäßig von Scala definiert. Es handelt sich dabei um einen Thread-Pool, der genau so viele Threads hat, wie beispielsweise mein Rechner CPUs. Also für einen Rechner mit acht Cores wären das acht Threads. Nun könnte ich beispielsweise einen Test machen, um diese Funktion "postToUrl" aufzurufen, beispielsweise in der Form einer Konstante "postFuture", die sich dadurch definiert, dass ich "postToUrl" nutze und in dem Fall die API aufrufe mit dem Statuscode "200". Auf einem solchen Future kann ich nun also Operationen ausführen. In meinem Fall möchte ich nun also das Resultat dieses Future sehen, in dem Fall also den Inhalt einer Website. Ich nutze also hier "foreach" und gebe den Inhalt auf die PrintLine-Funktion aus. Als nächstes könnte ich nun noch kurz etwas auf die Konsole schreiben, beispielsweise "End". Ich führe die Anwendung nun aus und sehe, dass sie geendet ist. Das Problem daran ist, dass die Anwendung nicht darauf wartet, dass dieses Future abgeschlossen wird, denn dieser Code hier ist nicht blockierend. Das bedeutet, es wird automatisch hier die Ausgabe getätigt und die Anwendung wird beendet. Was ich nun machen könnte, ist beispielsweise auf dem Hauptthread zu schlafen. Zum Beispiel für fünf Sekunden. In dem Fall erhalte ich also nun die Ausgabe in Form des Inhalts der Website, also wurde mein Future nun erfüllt. Allerdings kann ich auch hier einen besonderen Mechanismus nutzen, um beispielsweise auch auf ein Future innerhalb solches Codes warten zu können. Das ist möglich, indem ich zum Beispiel hier die Hilfsfunktion auf "Await" nutze, in dem Fall ist es "Await.result" Dies erwartet nun also ein Future, in meinem Fall also das "postFuture" und eine Zeit, die maximal gewartet werden darf. Dieser Mechanismus ist sehr sinnvoll, denn so kann man verhindern, dass die Applikation unendlich, beispielsweise, auf ein solches Future wartet. Standardmäßig sind in Scala alle Hilfsfunktionen rund um Futures so implementiert, dass sie eine Maximalzeit definieren, die sie beispielsweise warten. So kommt der Entwickler nicht in die Verlegenheit, aus Versehen einen Deadlock zu bauen. Es wird also von mir eine "Duration" erwartet, diese ist im Package "concurrent.duration" definiert. Ich gebe hier nun beispielsweise eine Zeit an, in dem Fall also "5" und eine "TimeUnit", hier also Sekunden. Das Ganze kann ich nun auch auf die Konsole ausgeben, um zu sehen, was der Inhalt der Webseite ist. Anders als im vorherigen Beispiel wird nun also auf das Resultat gewartet und dann die Applikation unmittelbar beendet. Die Einführung solcher Futures hat dann auch meistens sehr weitreichende Konsequenzen. Das heißt, idealerweise designt man seine API von Anfang an so, dass sie auf Futures ausgerichtet ist, insofern man weiß, dass alle Folgemethoden davon abhängig sind, dass beispielsweise ein Aufruf im Internet getätigt wird. Wenn ich nun also diese Anpassungen machen möchte, würde ich zunächst einmal ein ExecutionContext delegieren. Das bedeutet, ein anderer Teil der Applikation ist dafür verantwortlich, dass es einen ExecutionContext gibt. Für konsumierende Funktionen der API, wie beispielsweise der "charge 1" und der "charge 2", müsste ich nun also einen Rückgabewert von "Future[String]" definieren und selbstverständlich auch hier einen impliziten ExecutionContext entgegennehmen. Als nächstes ist man in der Pflicht festzustellen, welche Aufrufe von "charge 1" und "charge 2" mit diesem ExecutionContext als auch mit dem Future arbeiten müssen. Das ist im Normalfall ein sehr schwieriger Prozess im Nachhinein. Ich könnte nun beispielsweise versuchen meine Applikation zu kompilieren und würde in dem Moment gleich sehen, wo nun solche Probleme auftreten: Zum einen nun in meiner aktuellen Implementierung, schließlich hat diese auf dem existenten ExecutionContext aufgebaut. Als nächstes innerhalb des "CheckingAccount". Hier muss ich nun also auch die API anders entwerfen. Anders als um einen Boolean handelt es sich nun um ein Future eines Booleans, schließlich müssen wir auf das Resultat warten. In dem Moment gilt auch nicht mehr die Try-Semantik. Ich verwende nun also einen Aufruf der Payment API, in dem Fall also zuerst des Aufrufes "charge 1" und als nächstes muss ich hier einen "Recovery" implementieren. Ähnlich wie bei der Try-Semantik kann ich also Probleme abfangen. In dem Fall bekomme ich also eine "exception" und diese kann ich nun auf die Konsole ausgeben in Form von "Error occurred". Und ich muss dann noch einen Fallback bereitstellen, zum Beispiel durch einen Aufruf der "PaymentApi.charge2". Ich weise noch darauf hin und tätige hier den Aufruf. Allerdings ist das Future von "PaymentApi.charge1" und "charge2" vom Typ "String". Ich möchte allerdings hier nur überprüfen, ob das Ganze positiv verlaufen ist. Also "mappe" ich den Inhalt zu einem Wert "True". Sollte das Ganze nun überhaupt nicht funktioniert haben, kann ich dafür noch einen Fallback benutzen. In dem Fall also "fallbackTo" und hier muss ich nun wiederum ein Future angeben, in dem Fall also einen fixen Wert "Future.successful(false)", also immer dann, wenn der Aufruf nicht funktioniert hat für beide APIs. Die Operation auf Futures bringt mich also in die Verlegenheit, hier wiederum einen ExecutionContext zu definieren. Diesen könnte ich nun wiederum durchreichen oder selbst definieren. An dieser Stelle, stelle ich nun den ExecutionContext bereit. In Form einer Konstante "ExecutionContext.global". Bedingt dadurch, dass ich nun die Semantik der API "charge" verändert habe, muss ich das auch auf der "trait" tun. In dem Fall ist der Rückgabewert nun immer ein Future"[Boolean]". Und bei der Kreditkarte, die auch einen Boolean ausgeben müsste, verwende ich genauso ein Future. Allerdings, da es sich hier um einen statischen Wert handelt, verwende ich nun "Future.successful(false)". Da der Checkout-Prozess aktuell die Rechnung stellt, muss ich nun eine Anpassung für den Aufruf von "charge" tätigen, anders als mit If-Blöcken arbeite ich also nun mit einem Future. Der Aufruf von "basket.customer" erfolgt nun also auf "charge" und bekommt hier in Zukunft ein Future eines Booleans. Ich übergebe den Preis und stelle nun fest, dass mein "Customer" auch noch die Charge-Methode anpassen muss, hier ist also der "charge" noch ein Boolean. In dem Fall handelt es sich aber um ein Future eines Booleans. Die einzige weitere Änderung, die hier noch nötig ist, ist nun die Rückgabe nicht des Wertes "false", sondern eines Futures des Werts "false", also Future.successful.false". In dem Moment begebe ich mich nun wieder in den "Checkout-Handler" und kann nun ähnlich wie vorher überprüfen, ob das Ganze positiv verlaufen ist. Also im Fall "true" durch die Information "Charged customer" oder im Falle "false", durch die Information "Could not charge customer". Da ich hier eine Operation auf einem Future durchführe, muss ich wiederum auch einen ExecutionContext bereitstellen, auf dem das Ganze ausgeführt wird. In dem Fall tue ich das wiederum innerhalb dieser "trait". Zum Beispiel durch den "ExecutionContext.global". Ich verwende an dieser Stelle "collect", um weiterhin ein Future zu behalten, sodass ein Konsument des Aufrufes "confirm" feststellen kann, ob der "confirm" auch vollständig abgelaufen ist. Schließlich, wenn ich mit Futures arbeite, müssen alle Konsumenten der APIs immer wissen, ob das Future auch beendet wurde. In dem Fall ist also das Resultat nun, statt einer normalen "Unit", das "Future" einer "Unit", denn es existiert hier keine explizite Rückgabe. Der letzte Punkt, an dem das Ganze nun noch Auswirkungen hat, ist die Applikation selbst. Im Moment ist "confirm" hier noch ein normaler Aufruf und es wird davon ausgegangen, dass dieser blockierend ist. Allerdings möchte ich nur dann eine Rechnung drucken, wenn er tatsächlich auch abgelaufen ist. Daher nutze ich also nun das Mapping, um dort die Rechnung auszudrucken. Ich verwende deswegen hier ein Mapping, da ich mit diesem Future weiterhin arbeiten möchte. Denn ich möchte, dass der Applikationscode nun darauf wartet, dass der gesamte Prozess abgeschlossen ist. Das Ganze tue ich mit einem "Await.result" des Future für eine Maximalzeit von beispielsweise 3 Sekunden. Zu guter Letzt prüfe ich noch einmal, ob mein gesamter Code nun korrekt ist und an der Stelle fällt mir auf, dass ich, ähnlich wie auch in anderen Fällen, hier eine Operation auf einem Future durchführe. Aus diesem Grund benötige ich auch hier einen ExecutionContext. Diesen definiere ich also kurzerhand, über "implicit val context" und "ExecutionContext.global". Mein gesamter Code ist nun also abgelaufen und auch eine Rechnung wurde gedruckt. Man sieht also, dass das nachträgliche Einführen von Futures in eine Applikation, die bereits besteht und auf blockierenden Mechanismen aufbaut, sehr schwierig ist. Und das allein schon in einer Applikation mit nur so wenigen Klassen. Die Idee eines Futures in Scala ist also, dass man Statements innerhalb eines solchen Futures ausführt. Das heißt, das Future definiert einen Funktionsblock, innerhalb dessen Code abläuft und dessen Rückgabewert in das Future gekapselt wird. Mit einem solchen Future ist es dann möglich, Operationen auf diesem zukünftigen Wert auszuführen, unabhängig davon, ob er jetzt bereits existiert oder nicht. In dem Moment, wo man sich dafür entscheidet ein Future zu konsumieren, beispielsweise durch einen "Await.result" oder ein "Future.foreach", ist der Rückgabewert letztendlich das Ergebnis dieses Futures und damit ist dieses Future auch beendet. Innerhalb von Scala nutzen Futures einen sogenannten "ExecutionContext". Ein solcher "ExecutionContext" ist im Hintergrund nichts anderes als ein regulärer Thread-Pool. Allerdings wurde beim Design von Scala von Anfang an Wert darauf gelegt, dass genau ein solcher Thread-Pool verwendet wird und nicht für jede Aktion ein eigener Thread initialisiert wird. Die Nutzung des ExecutionContext funktioniert dann über Mechanismen, die man aus Scala kennt, in dem Fall also implizite Variablen. Damit ist es also möglich, die Definition des ExecutionContext an den Konsumenten der API zu delegieren. Des Weiteren stellt Scala einen globalen ExecutionContext zur Verfügung, insofern man sich nicht mit den Problemen eines ExecutionContext und des damit verbundenen Thread-Pools auseinandersetzen möchte.

Scala Grundkurs

Entdecken Sie die Möglichkeiten und Eigenschaften der modernen Programmiersprache Scala.

4 Std. 44 min (39 Videos)
Derzeit sind keine Feedbacks vorhanden...
 
Exklusiv für Abo-Kunden
Erscheinungsdatum:12.04.2017

Dieser Online-Kurs ist als Download und als Streaming-Video verfügbar. Die gute Nachricht: Sie müssen sich nicht entscheiden - sobald Sie das Training erwerben, erhalten Sie Zugang zu beiden Optionen!

Der Download ermöglicht Ihnen die Offline-Nutzung des Trainings und bietet die Vorteile einer benutzerfreundlichen Abspielumgebung. Wenn Sie an verschiedenen Computern arbeiten, oder nicht den ganzen Kurs auf einmal herunterladen möchten, loggen Sie sich auf dieser Seite ein, um alle Videos des Trainings als Streaming-Video anzusehen.

Wir hoffen, dass Sie viel Freude und Erfolg mit diesem Video-Training haben werden. Falls Sie irgendwelche Fragen haben, zögern Sie nicht uns zu kontaktieren!