2015-09-03 9 views
5

Biorąc pod uwagę następujące metody ...Dla zrozumienia: jak uruchomić Futures kolejno

def doSomething1: Future[Int] = { ... } 
def doSomething2: Future[Int] = { ... } 
def doSomething3: Future[Int] = { ... } 

... A po za-zrozumieniem:

for { 
    x <- doSomething1 
    y <- doSomething2 
    z <- doSomething3 
} yield x + y + z 

trzech metod prowadzone równolegle, ale w moim przypadku doSomething2 MUSI biec po doSomething1 skończył. Jak uruchomić kolejno trzy metody?

EDIT

Jak sugeruje Philosophus42, tutaj poniżej jest możliwe wdrożenie doSomething1:

def doSomething1: Future[Int] = { 
    // query the database for customers younger than 40; 
    // `find` returns a `Future` containing the number of matches 
    customerService.find(Json.obj("age" -> Json.obj("$lt" -> 40))) 
} 

... więc Future tworzony jest przez wewnętrzne wezwanie do innej metody.

EDIT 2

Być może uprościć sprawę używać zbyt dużo ... i przepraszam. Spróbujmy jeszcze raz i bliżej rzeczywistego przypadku użycia. Oto trzy metody:

for { 
    // get all the transactions generated by the exchange service 
    transactions <- exchange.orderTransactions(orderId) 

    //for each transaction create a log 
    logs <- Future.sequence(tansactions.map { transaction => 
    for { 
     // update trading order status 
     _ <- orderService.findAndUpdate(transaction.orderId, "Executed") 

     // create new log 
     log <- logService.insert(Log(
     transactionId => transaction.id, 
     orderId => transaction.orderId, 
     ... 
    )) 
    } yield log 
    }) 
} yield logs 

Co próbuję zrobić, to utworzyć dziennik dla każdej transakcji związanej z zamówieniem. logService.insert jest wielokrotnie wywoływana, nawet jeśli transactions zawiera tylko jeden wpis.

+4

Czy masz próbkę kodu, która się nie udała? Ponieważ dla zrozumienia wystarczy wykonać Futures w sekwencjach, jeśli nie są one tworzone poza {...}. – Philosophus42

+0

"Przyszłość" jest tworzona przez wewnętrzne wywołanie innej metody ... zobacz mój zaktualizowany wpis. – j3d

+0

j3d: Jakie dokładnie metody ** nie ** będą wykonywane równolegle? Jestem zirytowany, ponieważ mówisz głównie o liczbie wywołań 'logService.insert'. Proszę podać działający fragment i/lub podać więcej informacji na temat tego, co dokładnie robią metody. –

Odpowiedz

7

komentarz na swoim stanowisku

pierwsze, w jaki sposób kod wewnątrz doSomethingX wygląd lubić? Jeszcze bardziej zirytowany, że przy danym kodzie, futures przebiegają równolegle.

Odpowiedź

W celu dokonania sekwencyjny Future wykonania, wystarczy użyć

for { 
    v1 <- Future { ..block1... } 
    v2 <- Future { ..block2... } 
} yield combine(v1, v2) 

powód to działa, jest to, że oświadczenie Future {..} ..body zaczyna asynchroniczny obliczeń, na które punkt w czasie ocena jest oceniana.

Z powyższego For-pojmowania odcukrzona

Future { ..block1.. } 
    .flatMap(v1 => 
    Future { ..block>.. } 
     .map(v2 => combine(v1,v2)) 
) 

jest oczywiste, że

  • jeśli Future{ ...block1... } ma to wynik dostępny,
  • metoda flatMap jest wyzwalany, który
  • następnie wyzwala wykonanie Future { ...block2... }.

Zatem Future { ...block2... } jest wykonywany poFuture { ...block1... }

Dodatkowe informacje

Future

Future { 
    <block> 
} 

natychmiast powoduje wykonanie zamkniętego bloku przez ExecutionContext

Fragment 1:

val f1 = Future { <body> } 
val f2 = Future { <otherbody> } 

Dwa obliczenia są wyświetlane równolegle (w przypadku, gdy ExecutionContext jest ustawiony w ten sposób), jak te dwie wartości są oceniane natychmiast.

Fragment 2:

Konstrukt

def f1 = Future { ..... } 

rozpocznie realizację przyszłości, gdy f1 nazywa

Edit:

j3d, nadal jestem zdezorientowany , dlaczego Twój kod nie działa zgodnie z oczekiwaniami, jeśli twoje zdanie jest poprawne, że Przyszłość jest tworzony w ramach metodcomputeSomethingX.

Oto kod, który dowodzi, że computeSomething2 jest wykonywany po computeSomething1

importu scala.concurrent. {Await Future} importu scala.concurrent.duration._

object Playground { 

    import scala.concurrent.ExecutionContext.Implicits.global 

    def computeSomething1 : Future[Int] = { 
    Future { 
     for (i <- 1 to 10) { 
     println("computeSomething1") 
     Thread.sleep(500) 
     } 
     10 
    } 
    } 

    def computeSomething2 : Future[String] = { 
    Future { 
     for(i <- 1 to 10) { 
     println("computeSomething2") 
     Thread.sleep(800) 
     } 
     "hello" 
    } 
    } 

    def main(args: Array[String]) : Unit = { 

    val resultFuture: Future[String] = for { 
     v1 <- computeSomething1 
     v2 <- computeSomething2 
    } yield v2 + v1.toString 

    // evil "wait" for result 

    val result = Await.result(resultFuture, Duration.Inf) 

    println(s"Result: ${result}") 
    } 
} 

z wyjściem

computeSomething1 
computeSomething1 
computeSomething1 
computeSomething1 
computeSomething1 
computeSomething1 
computeSomething1 
computeSomething1 
computeSomething1 
computeSomething1 
computeSomething2 
computeSomething2 
computeSomething2 
computeSomething2 
computeSomething2 
computeSomething2 
computeSomething2 
computeSomething2 
computeSomething2 
computeSomething2 
Result: hello10 

Edycja 2

Jeśli chcesz, żeby być wykonywane w równoległego, tworzyć przyszłość wcześniej (tutaj f1 i f2)

def main(args: Array[String]) : Unit = { 
    val f1 = computeSomething1 
    val f2 = computeSomething2 

    val resultFuture: Future[String] = for { 
    v1 <- f1 
    v2 <- f2 
    } yield v2 + v1.toString 

    // evil "wait" for result 

    val result = Await.result(resultFuture, Duration.Inf) 

    println(s"Result: ${result}") 
} 
+0

Dziękuję bardzo (patrz także mój zaktualizowany post, aby zobaczyć, jak może wyglądać 'doSomething1'). Tylko ostatnie pytanie dotyczące wydajności ... czy istnieje jakiś znaczny narzut w przekazywaniu metod, które zwracają "Przyszłość" do "Future.apply", jak sugeruje to Ty? – j3d

+0

@ j3d: Zobacz nową część w mojej odpowiedzi. –

+0

Zobacz moją drugą aktualizację mojego wpisu ... Dodałem przykład, który jest bardzo zbliżony do mojego prawdziwego przypadku użycia. – j3d

0

widzę dwa warianty do osiągnięcia to:

pierwszy: upewnić, że Futures są tworzone wewnątrz do zrozumienia. Oznacza to, że twoje funkcje powinny być zdefiniowane w następujący sposób: def doSomething1: Future[Int] = Future { ... }. W takim przypadku za zrozumieniem należy wykonać Futures w kolejności.

drugie: Użyj funkcji map of the Future, że trzeba zakończyć zanim inni start:

doSomething1.map{ i => 
    for { 
    y <- doSomething2 
    z <- doSomething3 
    } yield i + y + z 
}