Future

Scala bietet eine einfache Möglichkeit, parallele Codeausführung in Form von Futures und Callbacks zu verwalten. Futures sind ein spezielles Objekt, das verwendet wird, um einen Wert (normalerweise ein Ergebnis von Berechnungen) zu speichern, der in der Zukunft verfügbar sein kann. Zukunft verfügbar sein wird. Callback ist eine Methode, die ausgeführt wird, wenn Future abgeschlossen ist. Um Futures in unserer Anwendung zu verwenden Anwendung zu verwenden, müssen wir zuerst das scala.concurrent Paket importieren und einen Ausführungskontext. Wenn Sie mit einer Play-Framework-Anwendung arbeiten, ist der einfachste Weg, einen Ausführungs Kontext zu erhalten, ist die Verwendung des Standard-Thread-Pools von Play. Sie können darauf mit @Inject()(implizit ec: Ausführungskontext).

Um einen Codeblock gleichzeitig auszuführen, müssen wir die Methode Future.apply aufrufen – wie im folgenden Beispiel gezeigt unten gezeigt.

val futureValue: Future[TypeA] = Future {
   someCode() // Some method that returns value of type TypeA
}

Callbacks

Die Methode Future.apply gibt das Future-Objekt zurück, das den Rückgabewert der Funktion someCode() enthält. Um darauf zuzugreifen, müssen wir einen Callback hinzufügen. Hier gibt es einige Möglichkeiten:

  • onComplete
  • foreach
  • map

Die erste – onComplete – gibt uns Zugang zu zwei Fällen:

  • Success[T] – die den Rückgabewert der erfolgreich abgeschlossenenFuture
  • Failed[T] – die den Wert der ausgelösten Ausnahme bei einem Fehlschlag enthalten Future
futureValue.onComplete {
   case Success(value) => someCode(value) // Some computation performed on value with type TypeA
   case Failure(exception) => println("Error: " + exception.getMessage)
}

Die zweite und dritte Option ermöglicht es uns, nur ein erfolgreiches Ergebnis zu verarbeiten. Sowohl onComplete als auch foreach haben einen Ergebnistyp von Unit, der uns daran hindert, sie mit anderen Rückrufen zu verketten. Sie werden meist für Seiteneffekte verwendet.

Die letzte Option – map – ermöglicht es uns, Ergebnisse auf einen beliebigen Typ abzubilden und gibt sie in einem neuen Future zurück, nachdem Fertigstellung des ursprünglichen Future zurück.

 

val futureValue: Future[TypeA] = Future {
   someCode() // Some method that return value of type TypeA
}

val futureValue2: Future[TypeB] = futureValue.map{ value =>
   someOtherMethod(value) // Some other method of type TypeA => TypeB
}

Parallele Ausführung

Manchmal müssen wir auf Werte von zwei oder mehr verschiedenen Futures zugreifen. Eine Möglichkeit, mit diesem Problem umzugehen
ist die Verschachtelung eines Callbacks eines Futures in einem anderen Callback – wie im folgenden Beispiel gezeigt.

val future1: Future[TypeA] = Future{
   someCode()
}

val future2: Future[TypeB] = Future{
   someOtherCode()
}

val future3: Future[Future[TypeC]] = future1.map{ value1 =>
   future2.map{ value2 =>
       someMethodHere(value1, value2) // method of type (TypeA, TypeB) => TypeC
   } // returns Future[TypeC]
}

Wie wir sehen können, ist dies nicht der sauberste Ansatz. Noch wichtiger ist, dass der Rückgabetyp unserer verschachtelten Maps
verschachtelte Future sein, was den Zugriff auf ihr Ergebnis erheblich erschwert. Aus diesem Grund haben Future
flatMap- und withFilter-Kombinatoren, die es erlauben, sie in for-Verständnissen zu verwenden. Der gleiche
Code wie oben, aber mit for-Komprehension würde wie folgt aussehen:

val future1: Future[TypeA] = Future{
   someCode()
}

val future2: Future[TypeB] = Future{
   someOtherCode()
}

val future: Future[TypeC] = for {
   value1 <- future1
   value2 <- future2
} yield {
   someMethodHere(value1, value2) // method of type (TypeA, TypeB) => TypeC
}

Wann wird Future ausgeführt?

Wann wird die Future ausgeführt? dass man sie richtig einsetzt. Wenn wir zwei Codeblöcke vergleichen:

// Block 1
val future1: Future[TypeA] = Future{
   someCode()
}

val future2: Future[TypeB] = Future{
   someOtherCode()
}

val future = for {
   value1 <- future1
   value2 <- future2
} yield {
   ...
}
// Block 2
val future = for {
   value1 <- Future{someCode()}
   value2 <-  Future{someOtherCode()}
} yield {
   ...
}

Auf den ersten Blick mag es so aussehen, als würden beide das Gleiche tun: zwei Methoden parallel zueinander durchführen. In Wirklichkeit wird nur wird nur die erste Methode gleichzeitig ausgeführt. Da for-Verständnisse auf (manchmal) verschachtelte foreach erweitert werden, map, flatMap und withFilter erweitert wurden, würde der zweite Block zu einem Code führen, der ähnlich wie dieser

Future{someCode()}.flatMap{value1 => Future{someOtherCode()}.map{value2 => ... }}

Das bedeutet, dass zuerst someCode() ausgeführt wird, und nach dessen Beendigung someOtherCode() gestartet. Scala startet die Ausführung von Code in Future.apply, wenn die Instanz erstellt wird, nicht wenn sie ausgewertet wird. Indem wir future einem Wert außerhalb von for comprehensions zuweisen, starten wir beide und weisen ihnen dann Callback Methoden in for-comprehensions zu.