Melhorando allOf e anyOf em CompletableFuture



Olá novamente. Antecipando o início do curso, "Java Developer" preparou uma tradução de material útil para você.


Existem dois métodos no CompletableFuture cujo design me surpreende:

  • CompletableFuture # allOf
  • CompletableFuture # anyOf


Neste artigo, veremos o que há de errado com eles e como eles podem ser feitos mais convenientes.

CompletableFuture # allOf


Vamos dar uma olhada na assinatura do método:

 public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) { // ... } 


Há pelo menos duas questões controversas aqui:

  1. O método aceita vários objetos CompletableFuture que retornam objetos de tipos diferentes.
  2. O método retorna CompletableFuture , que retorna Void


Além disso, alguns podem não gostar do número variável de parâmetros, então vamos ver essa parte também.

CompletableFuture<Void> frequentemente usado como um sinal para concluir uma operação; no entanto, ao fazer uma pequena alteração na API, esse método pode ser usado como um dispositivo de sinalização e como uma transportadora dos resultados de todas as operações concluídas . Vamos tentar fazer isso.

CompletableFuture assíncrono # allOf


Primeiro, vamos criar a assinatura certa.

É justo supor que na maioria dos casos seja necessário processar uma lista de CompletableFuture homogêneo e retornar um CompletableFuture contendo uma lista de resultados:

 public static <T> CompletableFuture<List<T>> allOf( Collection<CompletableFuture<T>> futures) { // ... } 


As entranhas do método original são provavelmente mais complexas do que o esperado:

 static CompletableFuture<Void> andTree( CompletableFuture<?>[] cfs, int lo, int hi) { CompletableFuture<Void> d = new CompletableFuture<Void>(); if (lo > hi) // empty d.result = NIL; else { CompletableFuture<?> a, b; int mid = (lo + hi) >>> 1; if ((a = (lo == mid ? cfs[lo] : andTree(cfs, lo, mid))) == null || (b = (lo == hi ? a : (hi == mid+1) ? cfs[hi] : andTree(cfs, mid+1, hi))) == null) throw new NullPointerException(); if (!d.biRelay(a, b)) { BiRelay<?,?> c = new BiRelay<>(d, a, b); a.bipush(b, c); c.tryFire(SYNC); } } return d; } 


Portanto, em vez de criá-lo do zero, tentaremos reutilizar o que já está no método original, como se fosse destinado a ser usado como um dispositivo de sinalização de conclusão ... e, em seguida, basta alterar o resultado nulo para a lista futura:

 CompletableFuture<List<CompletableFuture<T>>> i = futures.stream() .collect(collectingAndThen( toList(), l -> CompletableFuture.allOf(l.toArray(new CompletableFuture[0])) .thenApply(__ -> l))); 


Até agora tudo bem. Nós conseguimos
CompletableFuture<List<CompletableFuture<T> > > CompletableFuture<Void> vez de CompletableFuture<Void> , que já é bom. Mas não precisamos de uma lista de resultados futuros, precisamos de uma lista de resultados.

Agora podemos simplesmente processar a lista e remover o futuro indesejado dela. É perfeitamente normal chamar os métodos de junção CompletableFuture #, porque sabemos que eles nunca serão bloqueados (neste momento, todo o futuro já está concluído):

 CompletableFuture<List<T>> result = intermediate .thenApply(list -> list.stream() .map(CompletableFuture::join) .collect(toList())); 


Agora vamos combinar tudo isso em uma solução final:

 public static <T> CompletableFuture<List<T>> allOf( Collection<CompletableFuture<T>> futures) { return futures.stream() .collect(collectingAndThen( toList(), l -> CompletableFuture.allOf(l.toArray(new CompletableFuture[0])) .thenApply(__ -> l.stream() .map(CompletableFuture::join) .collect(Collectors.toList())))); } 


Conclusão assíncrona e em quedaFuture # allOf


Se houver exceções, o CompletableFuture # allOf original aguarda a conclusão de todas as operações restantes.

E se quisermos relatar a conclusão da operação quando ocorrer uma exceção nela, teremos que alterar a implementação.

Para fazer isso, crie uma nova instância do CompletableFuture e encerre-a manualmente após uma das operações gerar uma exceção:

 CompletableFuture<List<T>> result = new CompletableFuture<>(); futures.forEach(f -> f .handle((__, ex) -> ex == null || result.completeExceptionally(ex))); 


... mas precisamos lidar com o cenário em que todo o futuro será concluído com sucesso. Isso pode ser feito facilmente usando o método allOf () aprimorado e, em seguida, simplesmente finalize o futuro manualmente:

 allOf(futures).thenAccept(result::complete); 


Agora podemos combinar tudo para formar a solução final:

 public static <T> CompletableFuture<List<T>> allOfShortcircuiting(Collection<CompletableFuture<T>> futures) { CompletableFuture<List<T>> result = new CompletableFuture<>(); for (CompletableFuture<?> f : futures) { f.handle((__, ex) -> ex == null || result.completeExceptionally(ex)); } allOf(futures).thenAccept(result::complete); return result; } 


CompletableFuture # anyOf


Vamos começar com a assinatura do método:

 public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) { // ... } 


Podemos detectar imediatamente os mesmos problemas do método discutido acima:

  1. O método aceita vários objetos CompletableFuture que contêm objetos de tipos diferentes.
  2. O método retorna um CompletableFuture contendo um objeto do tipo Object .


Tanto quanto eu entendo, o CompletableFuture#allOf foi projetado para ser usado como um dispositivo de sinalização. Mas CompletableFuture#anyOf não segue essa filosofia, retornando um CompletableFuture<Object> , que é ainda mais confuso.

Dê uma olhada no exemplo a seguir, onde estou tentando processar o CompletableFuture que contém dados de diferentes tipos:

 CompletableFuture<Integer> f1 = CompletableFuture.completedFuture(1); CompletableFuture<String> f2 = CompletableFuture.completedFuture("2"); Integer result = CompletableFuture.anyOf(f1, f2) .thenApply(r -> { if (r instanceof Integer) { return (Integer) r; } else if (r instanceof String) { return Integer.valueOf((String) r); } throw new IllegalStateException("unexpected object type!"); }).join(); 


Bastante desconfortável, não é?

Felizmente, isso é muito fácil de se adaptar a um cenário mais comum (aguardando um dos muitos futuros que contêm valores do mesmo tipo) alterando a assinatura e introduzindo a conversão direta do tipo.

Assim, com nossas melhorias, podemos reutilizar métodos existentes e trazer com segurança o resultado:

 public static <T> CompletableFuture<T> anyOf(List<CompletableFuture<T>> cfs) { return CompletableFuture.anyOf(cfs.toArray(new CompletableFuture[0])) .thenApply(o -> (T) o); } public static <T> CompletableFuture<T> anyOf(CompletableFuture<T>... cfs) { return CompletableFuture.anyOf(cfs).thenApply(o -> (T) o); } 


Código fonte


O código fonte pode ser encontrado no Github .

Só isso. Vejo você no curso .

Source: https://habr.com/ru/post/pt481804/


All Articles