2017-02-27 20 views
5

Mam 3 CompletableFutures wszystkie 3 zwracające różne typy danych.Java 8 Completable Futures allOf różnych typów danych

Szukam utworzyć obiekt wynikowy, który jest składem wyniku zwróconego przez wszystkie 3 kontrakty terminowe.

Więc mój obecny kodeks pracy wygląda następująco:

public ClassD getResultClassD() { 

    ClassD resultClass = new ClassD(); 
    CompletableFuture<ClassA> classAFuture = CompletableFuture.supplyAsync(() -> service.getClassA()); 
    CompletableFuture<ClassB> classBFuture = CompletableFuture.supplyAsync(() -> service.getClassB()); 
    CompletableFuture<ClassC> classCFuture = CompletableFuture.supplyAsync(() -> service.getClassC()); 

    CompletableFuture.allOf(classAFuture, classBFuture, classCFuture) 
        .thenAcceptAsync(it -> { 
         ClassA classA = classAFuture.join(); 
         if (classA != null) { 
          resultClass.setClassA(classA); 
         } 

         ClassB classB = classBFuture.join(); 
         if (classB != null) { 
          resultClass.setClassB(classB); 
         } 

         ClassC classC = classCFuture.join(); 
         if (classC != null) { 
          resultClass.setClassC(classC); 
         } 

        }); 

    return resultClass; 
} 

Moje pytania są następujące:

  1. Moim założeniem jest to, że ponieważ używam allOf i thenAcceptAsync to wezwanie będzie zakaz blokowania. Czy moje zrozumienie jest właściwe?

  2. Czy jest to właściwy sposób radzenia sobie z wieloma kontraktami terminowymi, które zwracają różne typy wyników?

  3. Czy słusznie jest skonstruować obiekt ClassD w obrębie thenAcceptAsync?

  4. Czy jest właściwe stosowanie metody join lub getNow w lambda ThenAcceptAsync?

Odpowiedz

6

Twoja próba idzie we właściwym kierunku, ale nie jest poprawna. Twoja metoda getResultClassD() zwraca już utworzony obiekt typu ClassD, na którym arbitralny wątek wywoła metody modyfikujące, bez zwracania uwagi przez wywołującego z getResultClassD(). Może to powodować warunki wyścigu, jeśli metody modyfikacji nie są same bezpieczne dla wątków, ponadto osoba dzwoniąca nigdy się nie dowie, kiedy instancja ClassD jest rzeczywiście gotowa do użycia.

Prawidłowe rozwiązanie byłoby:

public CompletableFuture<ClassD> getResultClassD() { 

    CompletableFuture<ClassA> classAFuture 
     = CompletableFuture.supplyAsync(() -> service.getClassA()); 
    CompletableFuture<ClassB> classBFuture 
     = CompletableFuture.supplyAsync(() -> service.getClassB()); 
    CompletableFuture<ClassC> classCFuture 
     = CompletableFuture.supplyAsync(() -> service.getClassC()); 

    return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture) 
     .thenApplyAsync(dummy -> { 
      ClassD resultClass = new ClassD(); 

      ClassA classA = classAFuture.join(); 
      if (classA != null) { 
       resultClass.setClassA(classA); 
      } 

      ClassB classB = classBFuture.join(); 
      if (classB != null) { 
       resultClass.setClassB(classB); 
      } 

      ClassC classC = classCFuture.join(); 
      if (classC != null) { 
       resultClass.setClassC(classC); 
      } 

      return resultClass; 
     }); 
} 

Teraz wywołujący getResultClassD() mogą korzystać wracającą CompletableFuture kwerendy stan postępu prac lub czynności zależnych od łańcucha lub użyj join() aby pobrać wynik, po zakończeniu operacji .

Aby odpowiedzieć na inne pytania, tak, ta operacja jest asynchroniczna i użyteczne jest użycie join() w wyrażeniach lambda. join został właśnie utworzony, ponieważ Future.get(), który jest zadeklarowany jako wyjątek, sprawił, że użycie w tych wyrażeń lambda było niepotrzebnie trudne.

Należy pamiętać, że testy null są przydatne tylko wtedy, gdy te service.getClassX() mogą rzeczywiście powrócić null. Jeśli jedno z zgłoszeń serwisowych zakończy się niepowodzeniem z wyjątkiem, cała operacja (reprezentowana przez CompletableFuture<ClassD>) zakończy się wyjątkowo.

+0

dzięki za szczegółową odpowiedź. Moja jedyna odpowiedź na twoją odpowiedź brzmi, że thenApplyAsync ma typ zwrotu CompletableFuture , jak by to działało tutaj i jak by jeden powoływać się na tę metodę i spożywać wynik –

+4

Nie, jest to typ zwrotu 'allOf', który jest" CompletableFuture ' , dlatego funkcja przekazana 'thenApplyAsync' otrzymuje' Void' jako dane wejściowe (powyższy parametr 'dummy', zamiast' dummy -> ', możesz również napisać' (Puste manekiny) -> '). Następnie funkcja tłumaczy wejście 'Void' (przez jego ignorowanie) na wynik' ClassD', więc wynikiem 'thenApplyAsync' będzie' CompletableFuture '. – Holger

+1

@Holger Miałem podobną trasę do ciebie, ale używałem Opcjonalnego.Niepełnego w wywołaniach serwisowych, więc możesz mieć 'cCFuture.join(). IfPresent (class :: SetStuff)' – Ash

3

jadę w dół podobną drogę do tego, co @Holger robił w swojej odpowiedzi, ale owijania zgłoszeń serwisowych w opcjonalnym, co prowadzi do czystszego kodu w fazie thenApplyAsync

CompletableFuture<Optional<ClassA>> classAFuture 
    = CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassA()))); 

CompletableFuture<Optional<ClassB>> classBFuture 
    = CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassB())); 

CompletableFuture<Optional<ClassC>> classCFuture 
    = CompletableFuture.supplyAsync(() -> Optional.ofNullable(service.getClassC())); 

return CompletableFuture.allOf(classAFuture, classBFuture, classCFuture) 
    .thenApplyAsync(dummy -> { 
     ClassD resultClass = new ClassD(); 

     classAFuture.join().ifPresent(resultClass::setClassA) 
     classBFuture.join().ifPresent(resultClass::setClassB) 
     classCFuture.join().ifPresent(resultClass::setClassC) 

     return resultClass; 
    }); 
+1

tak, używając opcji, to właściwy sposób na pomyślenie o tym –

0

Innym sposobem obsługi to, jeśli nie chcesz zadeklarować, jak wiele zmiennych ma użyć funkcji Połącz ponownie lub Połącz z Synchronizacją, aby połączyć razem swoją przyszłość.

public CompletableFuture<ClassD> getResultClassD() 
{ 
    return CompletableFuture.supplyAsync(ClassD::new) 
    .thenCombine(CompletableFuture.supplyAsync(service::getClassA), (d, a) -> { 
     d.setClassA(a); 
     return d; 
    }) 
    .thenCombine(CompletableFuture.supplyAsync(service::getClassB), (d, b) -> { 
     d.setClassB(b); 
     return d; 
    }) 
    .thenCombine(CompletableFuture.supplyAsync(service::getClassC), (d, c) -> { 
     d.setClassC(c); 
     return d; 
    }); 
} 

Moduły pobierające nadal będą uruchamiane asynchronicznie, a wyniki będą wykonywane w kolejności. Jest to w zasadzie inna opcja składni, aby uzyskać ten sam wynik.

+0

To prawdopodobnie dobre podejście, jeśli istnieją tylko 2 lub 3 kontrakty terminowe do połączenia. Jednak prawdopodobnie powinieneś zacząć od 'CompletableFuture.completedFuture (new ClassD())', ponieważ instancja prawdopodobnie nie jest warta asynchronicznego działania. W rzeczywistości można nawet utworzyć instancję w 'thenApply()' w pierwszej przyszłości. –

Powiązane problemy