¿Equivalente de Java de C # async / await?

151

Soy un desarrollador normal de C # pero ocasionalmente desarrollo aplicaciones en Java. Me pregunto si hay algún equivalente Java de C # async / await. En palabras simples, ¿cuál es el equivalente de Java de:

async Task<int> AccessTheWebAsync()
{ 
    HttpClient client = new HttpClient();
    var urlContents = await client.GetStringAsync("http://msdn.microsoft.com");
    return urlContents.Length;
}
usuario960567
fuente
77
Por qué esto sería bueno: devoluciones de llamada como la declaración Ir a nuestras generaciones de Miguel de Icaza.
andrewdotn
La solución actual de Java es no tratar con valores reales prefijados async, sino usar valores Futureo en su Observablelugar.
SD
1
No hay equivalente Y duele. Otra característica que falta para la que necesita soluciones alternativas y bibliotecas complicadas, sin alcanzar el mismo efecto que estas dos simples palabras.
spyro
Vale la pena señalar que durante mucho tiempo, los diseñadores de Java han tratado de mantener el bytecode de Java compatible con versiones anteriores solo con cambios en bibliotecas y Syntatic-sugar en torno a las características existentes. Vea el hecho de que los genéricos no almacenan información de tipo de tiempo de ejecución y los lambders se implementan como objetos que implementan una interfaz . async / await requeriría cambios muy grandes en el bytecode para ser posible y, por lo tanto, no esperaría verlo en Java en el corto plazo.
Philip Couling

Respuestas:

141

No, no hay ningún equivalente de async / await en Java, o incluso en C # antes de v5.

Es una característica de lenguaje bastante compleja para construir una máquina de estado detrás de escena.

Hay relativamente poco soporte de lenguaje para asincronía / concurrencia en Java, pero el java.util.concurrentpaquete contiene muchas clases útiles sobre esto. (No es exactamente equivalente a la Biblioteca paralela de tareas, pero la aproximación más cercana a ella).

Jon Skeet
fuente
15
@ user960567: No, mi punto es que es una función de lenguaje , no se puede poner únicamente en bibliotecas. No creo que haya ningún equivalente programado para Java 8 al menos.
Jon Skeet
10
@ user960567: debe distinguir entre la versión de C # que está usando y la versión de .NET que está usando. async / await es una función de lenguaje : se introdujo en C # 5. Sí, puede usar Microsoft.Bcl.Async para usar async / await apuntando a .NET 4, pero aún debe usar un compilador C # 5.
Jon Skeet
55
@rozar: No, en realidad no. Ya hay varias opciones para la asincronía, pero RxJava no cambia el idioma de la manera en que lo hizo C #. No tengo nada contra Rx, pero no es lo mismo que asíncrono en C # 5.
Jon Skeet
11
@DtechNet: Bueno, hay mucha maquinaria JVM que es asíncrona, sí ... eso es muy diferente de que haya características de lenguaje reales que admitan la asincronía. (Hubo mucha asincronía en .NET antes de async / wait, también ... pero async / await hace que sea mucho más fácil aprovechar eso.)
Jon Skeet
1
@Aarkon: Diría que a menos que haya un soporte de lenguaje explícito , la respuesta sigue siendo correcta. No es solo una cuestión de bibliotecas que hacen que la programación sea más simple: aquí es importante toda la forma en que el compilador de C # construye una máquina de estado.
Jon Skeet
41

El awaitutiliza una continuación para ejecutar código adicional cuando se complete la operación asíncrona ( client.GetStringAsync(...)).

Entonces, como la aproximación más cercana, usaría una solución basada en CompletableFuture<T>(el equivalente de Java 8 a .net Task<TResult>) para procesar la solicitud Http de forma asincrónica.

ACTUALIZADO el 25-05-2016 a AsyncHttpClient v.2 lanzado el 13 de abril de 2016:

Entonces, el equivalente de Java 8 al ejemplo de OP AccessTheWebAsync()es el siguiente:

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

Este uso se tomó de la respuesta a ¿Cómo obtengo un CompletableFuture de una solicitud Async Http Client? y que está de acuerdo con la nueva API proporcionada en la versión 2 de AsyncHttpClient lanzada el 13 de abril de 2016, que ya tiene soporte intrínseco CompletableFuture<T>.

Respuesta original con la versión 1 de AsyncHttpClient:

Para ello tenemos dos enfoques posibles:

  • el primero usa IO sin bloqueo y lo llamo AccessTheWebAsyncNio. Sin embargo, debido a que AsyncCompletionHandleres una clase abstracta (en lugar de una interfaz funcional), no podemos pasar una lambda como argumento. Por lo tanto, incurre en verbosidad inevitable debido a la sintaxis de las clases anónimas. Sin embargo, esta solución es la más cercana al flujo de ejecución del ejemplo C # dado .

  • el segundo es un poco menos detallado, sin embargo, presentará una nueva tarea que finalmente bloqueará un hilo f.get()hasta que se complete la respuesta.

Primer enfoque , más detallado pero sin bloqueo:

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;
}

Segundo enfoque menos detallado pero bloqueando un hilo:

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
fuente
1
En realidad, eso es el equivalente del flujo feliz. No cubre las excepciones de manejo, finalmente y otras. Incluirlos hará que el código sea mucho más complejo y propenso a errores.
haimb
1
Esto no es una continuación. Este ejemplo pierde el verdadero propósito de async / await, que es liberar el hilo actual para ejecutar otras cosas, y luego continuar la ejecución de este método en el hilo actual después de que llegue una respuesta. (Esto es necesario para que el subproceso de la interfaz de usuario responda o para reducir el uso de memoria). Lo que hace este ejemplo es una sincronización de subproceso de bloqueo simple, más algunas devoluciones de llamada.
Aleksandr Dubinsky
1
@AleksandrDubinsky Estoy de acuerdo con usted cuando señala que la devolución de llamada puede no ejecutarse en el hilo de la persona que llama. Tienes razón. No estoy de acuerdo con bloquear un hilo. Mi respuesta actualizada de ACTUALIZADO el 25-05-2016 es sin bloqueo.
Miguel Gamboa
1
.... y esta muestra es exactamente la razón por la cual C # es mucho más simple de escribir y leer cuando se hacen cosas asincrónicas. Es solo un dolor en Java.
spyro
29

Echa un vistazo a ea-async que reescribe el código de bytes de Java para simular async / wait bastante bien. Según su léame: "Está fuertemente inspirado por Async-Await en .NET CLR"

Hafthor
fuente
8
¿Alguien lo usa en producción?
Mr.Wang de Next Door
1
Parece que EA lo hace, no creo que gasten dinero en algo que no es adecuado para la producción.
BrunoJCM
1
Es bastante normal gastar dinero en algo y luego decidir que no es adecuado para la producción; Esa es la única forma de aprender. Es posible usarlo sin la configuración del agente de Java en producción; eso debería bajar un poco la barra ( github.com/electronicarts/ea-async ).
Thoredge
16

asíncrono y espera son azúcares sintácticos. La esencia de async y esperar es la máquina de estados. El compilador transformará su código asíncrono / espera en una máquina de estado.

Al mismo tiempo, para que async / wait sea realmente practicable en proyectos reales, necesitamos tener muchas funciones de biblioteca Async I / O ya implementadas. Para C #, la mayoría de las funciones de E / S sincronizadas originales tienen una versión Async alternativa. La razón por la que necesitamos estas funciones asíncronas es porque en la mayoría de los casos, su propio código asíncrono / espera se reducirá a algún método de biblioteca asíncrona.

Las funciones de biblioteca de la versión Async en C # son algo así como el concepto AsynchronousChannel en Java. Por ejemplo, tenemos AsynchronousFileChannel.read que puede devolver un futuro o ejecutar una devolución de llamada después de que se realiza la operación de lectura. Pero no es exactamente lo mismo. Todas las funciones asíncronas de C # devuelven tareas (similar a Future pero más potente que Future).

Entonces, digamos que Java admite async / await, y escribimos un 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);
}

Entonces me imagino que el compilador transformará el código asíncrono / espera original en algo como esto:

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;
}

Y aquí está la implementación de 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
fuente
15
Azúcar sintético? ¿Tiene alguna idea sobre cómo ajustar las excepciones alrededor del código asíncrono y los bucles alrededor del código asíncrono?
Akash Kava
39
Las clases también son azúcar sintáctica. El compilador crea todos los árboles y listas de punteros de función que normalmente escribiría a mano de forma totalmente automática. Estas funciones / métodos también son azúcar sintáctica. Generan automáticamente todos los gotos que normalmente, como un programador real, escriben a mano. Assembler también es azúcar sintáctico. Los programadores reales escriben manualmente el código de máquina y lo portan manualmente a todas las arquitecturas de destino.
yeoman
32
Pensando en ello, las computadoras en sí mismas son simplemente azúcar sintáctica para l4m3 n00bz. Los programadores reales les sueldan pequeños circuitos integrados a una tabla de madera y los conectan con alambre de oro porque las placas de circuito son azúcar sintáctica, al igual que la producción en masa, los zapatos o los alimentos.
yeoman
14

No existe un equivalente de C # async / await en Java a nivel de lenguaje. Un concepto conocido como Las fibras también conocido como hilos cooperativos también conocido como hilos ligeros podría ser una alternativa interesante. Puede encontrar bibliotecas Java que brinden soporte para fibras.

Bibliotecas Java que implementan Fibras

Puede leer este artículo (de Quasar) para obtener una buena introducción a las fibras. Cubre qué son los subprocesos, cómo se pueden implementar las fibras en la JVM y tiene algún código específico de Quasar.

FabienB
fuente
10
async / await en C # no es una fibra. Simplemente compila magia que usa la continuación en Promise (la Taskclase) al registrar una devolución de llamada.
UltimaWeapon
1
@UltimaWeapon Entonces, ¿qué consideras que son fibras?
Aleksandr Dubinsky
@AleksandrDubinsky Uno de los ejemplos es goroutine.
UltimaWeapon
1
@UltimaWeapon Estaba buscando una explicación.
Aleksandr Dubinsky
@AleksandrDubinsky Soy flojo para explicarlo. Si realmente quieres saberlo, puedes buscar el artículo bajo el capó de la gorutina.
UltimaWeapon
8

Como se mencionó, no existe un equivalente directo, pero se podría crear una aproximación muy cercana con modificaciones de código de bytes de Java (tanto para instrucciones asíncronas / de espera como para la implementación de continuaciones subyacentes).

Estoy trabajando en este momento en un proyecto que implementa async / await en la parte superior de la biblioteca de continuación JavaFlow , consulte https://github.com/vsilaev/java-async-await

Aún no se ha creado Maven mojo, pero puede ejecutar ejemplos con el agente Java suministrado. Así es como se ve el código asíncrono / espera:

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 es la anotación que marca un método como ejecutable asincrónicamente, await () es una función que espera en CompletableFuture usando continuaciones y una llamada a "return asyncResult (someValue)" es lo que finaliza CompletableFuture / Continuation asociado

Al igual que con C #, el flujo de control se conserva y el manejo de excepciones se puede hacer de manera regular (prueba / captura como en el código ejecutado secuencialmente)

Valery Silaev
fuente
6

Java en sí no tiene características equivalentes, pero existen bibliotecas de terceros que ofrecen una funcionalidad similar, por ejemplo, Kilim .

Alexei Kaigorodov
fuente
3
No creo que esta biblioteca tenga nada que ver con lo que hace async / await.
Natan
5

Primero, comprenda qué es async / await. Es una manera para que una aplicación GUI de subproceso único o un servidor eficiente ejecute múltiples "fibras" o "co-rutinas" o "subprocesos livianos" en un solo subproceso.

Si está de acuerdo con el uso de hilos comunes, entonces el equivalente de Java es ExecutorService.submityFuture.get . Esto bloqueará hasta que se complete la tarea y devolverá el resultado. Mientras tanto, otros hilos pueden hacer el trabajo.

Si desea el beneficio de algo como las fibras, necesita soporte en el contenedor (es decir, en el bucle de eventos GUI o en el controlador de solicitud HTTP del servidor), o escribiendo el suyo.

Por ejemplo, Servlet 3.0 ofrece procesamiento asincrónico. Ofertas de JavaFX javafx.concurrent.Task. Sin embargo, estos no tienen la elegancia de las características del lenguaje. Funcionan a través de devoluciones de llamadas ordinarias.

Aleksandr Dubinsky
fuente
2
Aquí hay una cotización del artículo que reinicia el primer párrafo de esta respuesta // comience la cotización Para aplicaciones cliente, como Windows Store, escritorio de Windows y aplicaciones de Windows Phone, el principal beneficio de async es la capacidad de respuesta. Este tipo de aplicaciones utilizan asíncrona principalmente para mantener la interfaz de usuario receptiva. Para las aplicaciones de servidor, el principal beneficio de async es la escalabilidad. msdn.microsoft.com/en-us/magazine/dn802603.aspx
granadaCoder
3

No hay nada nativo de Java que le permita hacer esto como palabras clave asíncronas / aguardadas, pero lo que puede hacer si realmente desea es usar un CountDownLatch . Usted podria entonces imitar asíncrono / Await pasando esto alrededor (al menos en Java7). Esta es una práctica común en las pruebas de unidades de Android donde tenemos que hacer una llamada asíncrona (generalmente una ejecutable publicada por un controlador) y luego esperar el resultado (cuenta atrás).

Sin embargo, usar esto dentro de su aplicación en lugar de su prueba NO es lo que recomiendo. Eso sería extremadamente de mala calidad, ya que CountDownLatch depende de que usted cuente la cantidad correcta de veces y en los lugares correctos.

stevebot
fuente
3

Realicé y lancé Java async / await library. https://github.com/stofu1234/kamaitachi

Esta biblioteca no necesita la extensión del compilador, y realiza el procesamiento de IO sin pila en 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
fuente
1

Desafortunadamente, Java no tiene equivalente de async / await. Probablemente, lo más cercano que puede obtener es con ListenableFuture from Guava y el encadenamiento del oyente, pero aún sería muy engorroso escribir para casos que involucran múltiples llamadas asincrónicas, ya que el nivel de anidación crecería muy rápidamente.

Si está de acuerdo con usar un idioma diferente además de JVM, afortunadamente hay async / await en Scala, que es un equivalente directo de C # async / await con una sintaxis y semántica casi idénticas: https://github.com/scala/ asíncrono /

Tenga en cuenta que aunque esta funcionalidad necesitaba un soporte de compilador bastante avanzado en C #, en Scala se podía agregar como una biblioteca gracias a un sistema macro muy potente en Scala y, por lo tanto, se puede agregar incluso a versiones anteriores de Scala como 2.10. Además, Scala es compatible con Java, por lo que puede escribir el código asíncrono en Scala y luego llamarlo desde Java.

También hay otro proyecto similar llamado Akka Dataflow http://doc.akka.io/docs/akka/2.3-M1/scala/dataflow.html que usa una redacción diferente pero conceptualmente es muy similar, sin embargo, implementado usando continuaciones delimitadas, no macros (por lo que funciona incluso con versiones anteriores de Scala como 2.9).

Piotr Kołaczkowski
fuente
1

Java no tiene el equivalente directo de la función del lenguaje C # llamada async / await, sin embargo, hay un enfoque diferente del problema que async / await intenta resolver. Se llama proyecto Loom , que proporcionará hilos virtuales para la concurrencia de alto rendimiento. Estará disponible en alguna versión futura de OpenJDK.

Este enfoque también resuelve el " problema de función coloreada " que tiene async / wait

Una característica similar también se puede encontrar en Golang ( goroutines ).

Martin Obrátil
fuente
0

Si solo busca un código limpio que simule el mismo efecto que async / await en java y no le importa bloquear el hilo que se llama hasta que finalice, como en una prueba, puede 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();
                }
            });
        }
    });
Bolita
fuente
0

La biblioteca Java AsynHelper incluye un conjunto de clases / métodos de utilidad para tales llamadas asincrónicas (y esperar).

Si se desea ejecutar un conjunto de llamadas a métodos o bloques de código de forma asincrónica, incluye un método auxiliar útil AsyncTask .submitTasks como en el siguiente fragmento.

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

Si se desea esperar hasta que todos los códigos asíncronos se completen, AsyncTask.submitTasksAndWait se puede usar AsyncTask.submitTasksAndWait.

Además, si se desea obtener un valor de retorno de cada una de las llamadas al método asincrónico o bloque de código, se puede usar AsyncSupplier .submitSuppliers para que el resultado se pueda obtener de la matriz de proveedores de resultados devuelta por el método. A continuación se muestra el fragmento de muestra:

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);

Si el tipo de retorno de cada método difiere, use el siguiente fragmento de fragmento.

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());

El resultado de las llamadas al método asincrónico / bloques de código también se puede obtener en un punto de código diferente en el mismo hilo o en un hilo diferente que en el fragmento de código siguiente.

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
fuente