Java equivalente de C # assíncrono / aguardar?

151

Sou desenvolvedor C # normal, mas ocasionalmente desenvolvo aplicativos em Java. Eu estou querendo saber se existe algum equivalente em Java de c # assíncrono / aguardar? Em palavras simples, qual é o equivalente java de:

async Task<int> AccessTheWebAsync()
{ 
    HttpClient client = new HttpClient();
    var urlContents = await client.GetStringAsync("http://msdn.microsoft.com");
    return urlContents.Length;
}
user960567
fonte
7
Por que isso seria bom: retornos de chamada como declaração Go To de nossas gerações de Miguel de Icaza.
andrewdotn
A solução atual do Java é não lidar com valores reais prefixados async, mas usar Futureou Observablevalores.
23418 SD
1
Não há equivalente. E isso machuca. Mais um recurso ausente, para que você precise de soluções e bibliotecas complicadas, sem nunca atingir o mesmo efeito que essas duas palavras simples.
Spyro #
Vale a pena notar que, durante muito tempo, os designers de Java tentaram manter o bytecode Java compatível com versões anteriores, apenas com alterações nas bibliotecas e no açúcar sintático em torno dos recursos existentes. Veja o fato de que os genéricos não armazenam informações do tipo em tempo de execução e os lambders são implementados como objeto implementando uma interface . o assíncrono / espera exigiria alterações muito grandes no bytecode para serem possíveis e, portanto, eu não esperaria vê-lo em java tão cedo.
Philip Couling

Respostas:

140

Não, não há equivalente a async / wait em Java - ou mesmo em C # antes da v5.

É um recurso de linguagem bastante complexo para construir uma máquina de estado nos bastidores.

Há relativamente pouco suporte à linguagem para assincronia / simultaneidade em Java, mas o java.util.concurrentpacote contém muitas classes úteis em torno disso. (Não é bem equivalente à Task Parallel Library, mas a aproximação mais próxima a ela.)

Jon Skeet
fonte
15
@ user960567: Não, meu argumento é que é um recurso de linguagem - não pode ser colocado apenas em bibliotecas. Não acredito que exista um equivalente agendado para Java 8, pelo menos.
Jon Skeet
10
@ user960567: Você precisa distinguir entre a versão do C # que você está usando e a versão do .NET que está usando. async / waitit é um recurso de linguagem - foi introduzido no C # 5. Sim, você pode usar o Microsoft.Bcl.Async para usar o async / waitit visando o .NET 4, mas você ainda precisa usar um compilador C # 5.
Jon Skeet
5
@rozar: Não, na verdade não. Já existem várias opções para assincronia - mas o RxJava não altera o idioma da maneira que o C # fez. Não tenho nada contra Rx, mas não é a mesma coisa que assíncrona em C # 5.
Jon Skeet
11
@ DtechNet: Bem, existem muitas máquinas JVM que são assíncronas, sim ... isso é muito diferente de existirem recursos de linguagem reais que suportam assincronia. (Havia um monte de assincronia no .NET antes async / await, também ... mas assíncrona / marcas esperam que muito mais fácil para tirar proveito disso.)
Jon Skeet
1
@Aarkon: eu diria que, a menos que haja suporte explícito ao idioma , a resposta ainda está correta. Não é apenas uma questão de bibliotecas que simplifica o agendamento - é importante aqui a maneira como o compilador C # constrói uma máquina de estado.
Jon Skeet
41

Ele awaitusa uma continuação para executar código adicional quando a operação assíncrona for concluída ( client.GetStringAsync(...)).

Portanto, como a aproximação mais próxima, eu usaria uma solução baseada em CompletableFuture<T>(o Java 8 equivalente a .net Task<TResult>) para processar a solicitação Http de forma assíncrona.

ATUALIZADO em 25-05-2016 para o AsyncHttpClient v.2 lançado em 13 de abril de 2016:

Portanto, o Java 8 equivalente ao exemplo de OP AccessTheWebAsync()é o seguinte:

CompletableFuture<Integer> AccessTheWebAsync()
{
    AsyncHttpClient asyncHttpClient = new DefaultAsyncHttpClient();
    return asyncHttpClient
       .prepareGet("http://msdn.microsoft.com")
       .execute()
       .toCompletableFuture()
       .thenApply(Response::getResponseBody)
       .thenApply(String::length);
}

Esse uso foi retirado da resposta de Como obtenho um CompletableFuture de uma solicitação de cliente HTTP assíncrono? e que está de acordo com a nova API fornecida na versão 2 do AsyncHttpClient lançada em 13 de abril de 2016, que já possui suporte intrínseco CompletableFuture<T>.

Resposta original usando a versão 1 do AsyncHttpClient:

Para esse fim, temos duas abordagens possíveis:

  • o primeiro usa IO sem bloqueio e eu o chamo AccessTheWebAsyncNio. No entanto, como AsyncCompletionHandleré uma classe abstrata (em vez de uma interface funcional), não podemos passar um lambda como argumento. Por isso, incorre em verbosidade inevitável devido à sintaxe de classes anônimas. No entanto, essa solução é a mais próxima do fluxo de execução do exemplo C # fornecido .

  • o segundo é um pouco menos detalhado, no entanto, enviará uma nova tarefa que, em última análise, bloqueará um encadeamento f.get()até a resposta ser concluída.

Primeira abordagem , mais detalhada, mas sem bloqueio:

static CompletableFuture<Integer> AccessTheWebAsyncNio(){
    final AsyncHttpClient asyncHttpClient = new AsyncHttpClient();
    final CompletableFuture<Integer> promise = new CompletableFuture<>();
    asyncHttpClient
        .prepareGet("https://msdn.microsoft.com")
        .execute(new AsyncCompletionHandler<Response>(){
            @Override
            public Response onCompleted(Response resp) throws Exception {
                promise.complete(resp.getResponseBody().length());
                return resp;
            }
        });
    return promise;
}

Segunda abordagem menos detalhada, mas bloqueando um thread:

static CompletableFuture<Integer> AccessTheWebAsync(){
    try(AsyncHttpClient asyncHttpClient = new AsyncHttpClient()){
        Future<Response> f = asyncHttpClient
            .prepareGet("https://msdn.microsoft.com")
            .execute();
        return CompletableFuture.supplyAsync(
            () -> return f.join().getResponseBody().length());
    }
}
Miguel Gamboa
fonte
1
Na verdade, isso é o equivalente ao fluxo feliz. Não abrange exceções de manipulação, finalmente e outras. A inclusão deles tornará o código muito mais complexo e mais propenso a erros.
haimb
1
Isso não é uma continuação. Este exemplo perde o objetivo real de async / waitit, que é liberar o encadeamento atual para executar outras coisas e, em seguida, continuar a execução desse método no encadeamento atual após a chegada de uma resposta. (Isso é necessário para que o thread da interface do usuário seja responsivo ou reduza o uso de memória.) O que este exemplo faz é uma sincronização simples do thread de bloqueio, além de alguns retornos de chamada.
Aleksandr Dubinsky
1
@AleksandrDubinsky Concordo com você quando você aponta que o retorno de chamada pode não ser executado no segmento de chamada. Você está certo. Não concordo em bloquear um tópico. Minha resposta atualizada de ATUALIZADO em 25-05-2016 é sem bloqueio.
Miguel Gamboa
1
.... e esse exemplo é exatamente o motivo pelo qual o C # é muito mais simples de escrever e ler ao fazer coisas assíncronas. É apenas uma dor em Java.
spyro
29

Confira o ea-async, que reescreve o bytecode Java para simular async / aguardar bastante. De acordo com o leia-me: "É fortemente inspirado pelo Async-Await no .NET CLR"

Hafthor
fonte
8
Alguém usa na produção?
Mr.Wang do Next Door
1
Parece que a EA faz, acho que eles não gastariam dinheiro com algo que não é adequado para produção.
precisa saber é o seguinte
1
É bastante normal gastar dinheiro com algo e depois decidir que não é adequado para produção; essa é a única maneira de aprender. É possível usá-lo sem as configurações do agente java na produção; isso deve abaixar um pouco a barra ( github.com/electronicarts/ea-async ).
thoredge 23/05
16

assíncrono e aguardar são açúcares sintáticos. A essência do assíncrono e da espera é a máquina de estado. O compilador transformará seu código assíncrono / aguardado em uma máquina de estado.

Ao mesmo tempo, para que o assíncrono / espera seja realmente praticável em projetos reais, precisamos ter muitas funções da biblioteca de E / S assíncrona já em funcionamento . Para C #, a maioria das funções de E / S sincronizadas originais possui uma versão Async alternativa. A razão pela qual precisamos dessas funções Async é que, na maioria dos casos, seu próprio código async / waitit se resume a algum método Async da biblioteca.

As funções da biblioteca de versões Async em C # são como o conceito AsynchronousChannel em Java. Por exemplo, temos AsynchronousFileChannel.read, que pode retornar um futuro ou executar um retorno de chamada após a conclusão da operação de leitura. Mas não é exatamente o mesmo. Todas as funções C # Async retornam Tarefas (semelhantes a Future, mas mais poderosas que Future).

Então, digamos que o Java suporte async / waitit, e escrevemos um código como este:

public static async Future<Byte> readFirstByteAsync(String filePath) {
    Path path = Paths.get(filePath);
    AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);

    ByteBuffer buffer = ByteBuffer.allocate(100_000);
    await channel.read(buffer, 0, buffer, this);
    return buffer.get(0);
}

Então eu imagino que o compilador transformará o código async / waitit original em algo como isto:

public static Future<Byte> readFirstByteAsync(String filePath) {

    CompletableFuture<Byte> result = new CompletableFuture<Byte>();

    AsyncHandler ah = new AsyncHandler(result, filePath);

    ah.completed(null, null);

    return result;
}

E aqui está a implementação do AsyncHandler:

class AsyncHandler implements CompletionHandler<Integer, ByteBuffer>
{
    CompletableFuture<Byte> future;
    int state;
    String filePath;

    public AsyncHandler(CompletableFuture<Byte> future, String filePath)
    {
        this.future = future;
        this.state = 0;
        this.filePath = filePath;
    }

    @Override
    public void completed(Integer arg0, ByteBuffer arg1) {
        try {
            if (state == 0) {
                state = 1;
                Path path = Paths.get(filePath);
                AsynchronousFileChannel channel = AsynchronousFileChannel.open(path);

                ByteBuffer buffer = ByteBuffer.allocate(100_000);
                channel.read(buffer, 0, buffer, this);
                return;
            } else {
                Byte ret = arg1.get(0);
                future.complete(ret);
            }

        } catch (Exception e) {
            future.completeExceptionally(e);
        }
    }

    @Override
    public void failed(Throwable arg0, ByteBuffer arg1) {
        future.completeExceptionally(arg0);
    }
}
caisx25
fonte
15
Açúcar sintático? Você tem alguma idéia de como quebrar exceções em torno do código assíncrono e fazer um loop em torno do código assíncrono?
Akash Kava 29/07
39
As aulas também são açúcar sintático. O compilador cria todas as árvores e listas de ponteiros de função que você normalmente escreveria à mão automaticamente para você. Essas funções / métodos também são açúcar sintático. Eles geram automaticamente todos os gotos que você normalmente, sendo um verdadeiro programador, escreve à mão. Assembler também é açúcar sintático. Programadores reais escrevem manualmente o código da máquina e portam-no manualmente para todas as arquiteturas de destino.
31516
32
Pensando nisso, os próprios computadores são apenas açúcar sintático para l4m3 n00bz. Programadores de verdade os soldam minúsculos circuitos integrados a uma placa de madeira e os conectam com fios de ouro porque as placas de circuito são açúcar sintático, assim como produção em massa, sapatos ou alimentos.
yeoman
14

Não há equivalente a C # assíncrono / aguardado em Java no nível da linguagem. Um conceito conhecido como Fibers, também conhecido como encadeamento cooperativo ou encadernação leve, pode ser uma alternativa interessante. Você pode encontrar bibliotecas Java fornecendo suporte para fibras.

Bibliotecas Java que implementam Fibers

Você pode ler este artigo (do Quasar) para uma boa introdução às fibras. Ele cobre o que são threads, como as fibras podem ser implementadas na JVM e possui algum código específico do Quasar.

FabienB
fonte
10
assíncrono / espera em C # não é uma fibra. É apenas uma mágica de compilador que usa continuação no Promise (a Taskclasse) registrando um retorno de chamada.
UltimaWeapon
1
@UltimaWeapon Então, o que você considera serem fibras?
Aleksandr Dubinsky
@AleksandrDubinsky Um dos exemplos é a goroutine.
UltimaWeapon
1
@UltimaWeapon Eu estava procurando uma explicação.
Aleksandr Dubinsky
@AleksandrDubinsky Tenho preguiça de explicar. Se você realmente quer saber, você pode pesquisar o artigo sobre sob o capô da goroutine.
UltimaWeapon
8

Como foi mencionado, não há equivalente direto, mas uma aproximação muito próxima pode ser criada com modificações de bytecode Java (para instruções assíncronas / tipo aguardar e implementação de continuações subjacentes).

Estou trabalhando agora em um projeto que implementa async / wait em cima de wait biblioteca de continuação JavaFlow , verifique https://github.com/vsilaev/java-async-await

Nenhum Maven mojo foi criado ainda, mas você pode executar exemplos com o agente Java fornecido. Aqui está como é o código assíncrono / aguardado:

public class AsyncAwaitNioFileChannelDemo {

public static void main(final String[] argv) throws Exception {

    ...
    final AsyncAwaitNioFileChannelDemo demo = new AsyncAwaitNioFileChannelDemo();
    final CompletionStage<String> result = demo.processFile("./.project");
    System.out.println("Returned to caller " + LocalTime.now());
    ...
}


public @async CompletionStage<String> processFile(final String fileName) throws IOException {
    final Path path = Paths.get(new File(fileName).toURI());
    try (
            final AsyncFileChannel file = new AsyncFileChannel(
                path, Collections.singleton(StandardOpenOption.READ), null
            );              
            final FileLock lock = await(file.lockAll(true))
        ) {

        System.out.println("In process, shared lock: " + lock);
        final ByteBuffer buffer = ByteBuffer.allocateDirect((int)file.size());

        await( file.read(buffer, 0L) );
        System.out.println("In process, bytes read: " + buffer);
        buffer.rewind();

        final String result = processBytes(buffer);

        return asyncResult(result);

    } catch (final IOException ex) {
        ex.printStackTrace(System.out);
        throw ex;
    }
}

@async é a anotação que sinaliza um método como executável de forma assíncrona, waitit () é uma função que espera no CompletableFuture usando continuações e uma chamada para "retornar asyncResult (someValue)" é o que finaliza o CompletableFuture / Continuation associado

Assim como no C #, o fluxo de controle é preservado e o tratamento de exceções pode ser feito de maneira regular (tentativa / captura como no código executado sequencialmente)

Valery Silaev
fonte
6

O próprio Java não possui recursos equivalentes, mas existem bibliotecas de terceiros que oferecem funcionalidade semelhante, por exemplo, Kilim .

Alexei Kaigorodov
fonte
3
Eu não acho que essa biblioteca tenha algo a ver com o que async / waitit faz.
Natan
5

Primeiro, entenda o que é assíncrono / espera. É uma maneira de um aplicativo GUI de thread único ou um servidor eficiente executar várias "fibras" ou "co-rotinas" ou "threads leves" em um único thread.

Se você concorda com o uso de encadeamentos comuns, o equivalente em Java é ExecutorService.submiteFuture.get . Isso bloqueará até que a tarefa seja concluída e retorne o resultado. Enquanto isso, outros threads podem funcionar.

Se você deseja o benefício de algo como fibras, precisa de suporte no contêiner (quero dizer no loop de eventos da GUI ou no manipulador de solicitações HTTP do servidor), ou escrevendo seu próprio.

Por exemplo, o Servlet 3.0 oferece processamento assíncrono. Ofertas JavaFX javafx.concurrent.Task. Porém, eles não têm a elegância dos recursos de idioma. Eles trabalham com retornos de chamada comuns.

Aleksandr Dubinsky
fonte
2
Aqui está uma cotação de artigo reiniciando o primeiro parágrafo desta resposta // start quote Para aplicativos clientes, como Windows Store, Windows desktop e Windows Phone, o principal benefício do assíncrono é a capacidade de resposta. Esses tipos de aplicativos usam async principalmente para manter a interface do usuário responsiva. Para aplicativos de servidor, o principal benefício do assíncrono é a escalabilidade. msdn.microsoft.com/en-us/magazine/dn802603.aspx
granadaCoder
3

Não há nada nativo no java que permita fazer isso como palavras-chave assíncronas / aguardadas, mas o que você pode fazer se realmente quiser é usar um CountDownLatch . Você pode imitar async / wait passando isso (pelo menos em Java7). Essa é uma prática comum nos testes de unidade do Android, onde temos que fazer uma chamada assíncrona (geralmente uma executável postada por um manipulador) e aguardar o resultado (contagem regressiva).

No entanto, usar isso dentro do aplicativo em oposição ao seu teste NÃO é o que estou recomendando. Isso seria extremamente péssimo, pois o CountDownLatch depende de você contar efetivamente o número certo de vezes e nos lugares certos.

stevebot
fonte
3

Crio e liberei a biblioteca Java async / waitit. https://github.com/stofu1234/kamaitachi

Essa biblioteca não precisa da extensão do compilador e realiza o processamento de E / S sem pilha em Java.

    async Task<int> AccessTheWebAsync(){ 
        HttpClient client= new HttpClient();
        var urlContents= await client.GetStringAsync("http://msdn.microsoft.com");
       return urlContents.Length;
    }

   ↓

    //LikeWebApplicationTester.java
    BlockingQueue<Integer> AccessTheWebAsync() {
       HttpClient client = new HttpClient();
       return awaiter.await(
            () -> client.GetStringAsync("http://msdn.microsoft.com"),
            urlContents -> {
                return urlContents.length();
            });
    }
    public void doget(){
        BlockingQueue<Integer> lengthQueue=AccessTheWebAsync();
        awaiter.awaitVoid(()->lengthQueue.take(),
            length->{
                System.out.println("Length:"+length);
            }
            );
    }
stofu
fonte
1

Infelizmente, o Java não tem equivalente de assíncrono / espera. O mais próximo que você pode chegar é provavelmente o ListenableFuture da Guava e o encadeamento do ouvinte, mas ainda seria muito complicado escrever para casos que envolvam várias chamadas assíncronas, pois o nível de aninhamento aumentaria muito rapidamente.

Se você está bem com o uso de um idioma diferente na JVM, felizmente existe o recurso assíncrono / aguardado no Scala, que é um equivalente C # assíncrono / aguardado direto com uma sintaxe e semântica quase idênticas: https://github.com/scala/ assíncrono /

Observe que, embora essa funcionalidade precise de um suporte de compilador bastante avançado em C #, no Scala, ela pode ser adicionada como uma biblioteca, graças a um sistema de macros muito poderoso no Scala e, portanto, pode ser adicionada até a versões mais antigas do Scala, como a 2.10. Além disso, o Scala é compatível com classe com Java, portanto, você pode escrever o código assíncrono no Scala e depois chamá-lo de Java.

Há também outro projeto semelhante chamado Akka Dataflow http://doc.akka.io/docs/akka/2.3-M1/scala/dataflow.html que usa palavras diferentes, mas conceitualmente é muito semelhante, porém implementado usando continuações delimitadas, não macros (por isso funciona com versões Scala ainda mais antigas, como 2.9).

Piotr Kołaczkowski
fonte
1

Java não possui equivalente direto ao recurso da linguagem C # chamado async / waitit, no entanto, existe uma abordagem diferente para o problema que o async / waitit tenta resolver. Chama-se projeto Loom , que fornecerá threads virtuais para simultaneidade de alto rendimento. Ele estará disponível em alguma versão futura do OpenJDK.

Essa abordagem também resolve o " problema da função colorida " que o async / waitit possui.

Recurso semelhante também pode ser encontrado em Golang ( goroutines ).

Martin Obrátil
fonte
0

Se você está logo após um código limpo que simula o mesmo efeito que assíncrono / aguardar em java e não se importa de bloquear o encadeamento em que é chamado até terminar, como em um teste, você pode usar algo como este código:

interface Async {
    void run(Runnable handler);
}

static void await(Async async) throws InterruptedException {

    final CountDownLatch countDownLatch = new CountDownLatch(1);
    async.run(new Runnable() {

        @Override
        public void run() {
            countDownLatch.countDown();
        }
    });
    countDownLatch.await(YOUR_TIMEOUT_VALUE_IN_SECONDS, TimeUnit.SECONDS);
}

    await(new Async() {
        @Override
        public void run(final Runnable handler) {
            yourAsyncMethod(new CompletionHandler() {

                @Override
                public void completion() {
                    handler.run();
                }
            });
        }
    });
Pellet
fonte
0

A biblioteca Java AsynHelper inclui um conjunto de classes / métodos utilitários para chamadas assíncronas (e espera).

Se desejar executar um conjunto de chamadas de método ou blocos de código de forma assíncrona, ele inclui um método auxiliar útil AsyncTask .submitTasks, como no snippet abaixo.

AsyncTask.submitTasks(
    () -> getMethodParam1(arg1, arg2),
    () -> getMethodParam2(arg2, arg3)
    () -> getMethodParam3(arg3, arg4),
    () -> {
             //Some other code to run asynchronously
          }
    );

Se desejar aguardar até que todos os códigos assíncronos sejam executados, o variante AsyncTask.submitTasksAndWait pode ser usado.

Além disso, se desejar obter um valor de retorno de cada chamada de método assíncrona ou bloco de código, o AsyncSupplier .submitSuppliers pode ser usado para que o resultado possa ser obtido pela matriz de fornecedores de resultados retornada pelo método. Abaixo está o snippet de amostra:

Supplier<Object>[] resultSuppliers = 
   AsyncSupplier.submitSuppliers(
     () -> getMethodParam1(arg1, arg2),
     () -> getMethodParam2(arg3, arg4),
     () -> getMethodParam3(arg5, arg6)
   );

Object a = resultSuppliers[0].get();
Object b = resultSuppliers[1].get();
Object c = resultSuppliers[2].get();

myBigMethod(a,b,c);

Se o tipo de retorno de cada método for diferente, use o tipo de trecho abaixo.

Supplier<String> aResultSupplier = AsyncSupplier.submitSupplier(() -> getMethodParam1(arg1, arg2));
Supplier<Integer> bResultSupplier = AsyncSupplier.submitSupplier(() -> getMethodParam2(arg3, arg4));
Supplier<Object> cResultSupplier = AsyncSupplier.submitSupplier(() -> getMethodParam3(arg5, arg6));

myBigMethod(aResultSupplier.get(), bResultSupplier.get(), cResultSupplier.get());

O resultado do método assíncrono de chamadas / blocos de código também pode ser obtido em um ponto de código diferente no mesmo thread ou em um thread diferente do snippet abaixo.

AsyncSupplier.submitSupplierForSingleAccess(() -> getMethodParam1(arg1, arg2), "a");
AsyncSupplier.submitSupplierForSingleAccess(() -> getMethodParam2(arg3, arg4), "b");
AsyncSupplier.submitSupplierForSingleAccess(() -> getMethodParam3(arg5, arg6), "c");


//Following can be in the same thread or a different thread
Optional<String> aResult = AsyncSupplier.waitAndGetFromSupplier(String.class, "a");
Optional<Integer> bResult = AsyncSupplier.waitAndGetFromSupplier(Integer.class, "b");
Optional<Object> cResult = AsyncSupplier.waitAndGetFromSupplier(Object.class, "c");

 myBigMethod(aResult.get(),bResult.get(),cResult.get());
Loganathan
fonte