:index-group: EJB
:jbake-type: page
:jbake-status: status=published
= Métodos @Asynchronous

La annotación @Asynchronous fue introducida en EJB 3.1 como una manera simple
de crear procesamiento asíncrono.

Cada vez que un método anotado con `@Asynchronous` es invocado por cualquiera
retornará inmediatamentesin importar cuanto tarda en realidad el método. Cada
invocación retorna un objeto
http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Future.html[Future]
que esencialmente inicia _vacío_ y luego se llenará con su valor por el
contenedor cuando la llamada al metodo relacionado se ejecute en realidad.
Retornar un objeto `Future` no es requerido y un método `@Asynchronous` puede
por supuesto retornar `void`.

== Ejemplo

Aquí, en `JobProcessorTest`,

`final Future<String> red = processor.addJob("red");` procede a la siguiente sentencia,

`final Future<String> orange = processor.addJob("orange");`

sin esperar por a que método `addJob()` se complete. Y luego podríamos
preguntar por el resultado usando el método `Future<?>.get()` como sigue

`assertEquals("blue", blue.get());`

Espera a que el procesamiento de complete (si no se a completado aún) y
obtiene el resultado. Si no te importa el resultado, podrías simplemente tener tu método asíncrono como un método `void`.

Desde la documentación del Objeto http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Future.html[Future],

____
Un Future representa el resultado de un cómputo asíncrono. Se proporcionan métodos para chequear si el cómputo está completo, esperar por que se complete,
y para obtener el resultado del cómputo. El resultado solo puede ser obtenido
usando el método get cuando el cómputo se ha completado, bloqueando si es
necesario hasta que está listo. La cancelación es ejecutada por el método
cancel. Métodos adicionales son proporcionados para determinarsi la tarea se
completó normalmente o fue cancelada. Una vez que un cómputo se ha completado,
el cómputo no puede ser cancelado. Si quieres usar un Future solo por que se
puede cancelar pero sin proveer un resultado usable, puedes declarar tipos de
la forma Future<?> y retornar null como un resultado de la tarea subyacente
____

== El código

[source,java]
----
@Singleton
public class JobProcessor {
@Asynchronous
@Lock(READ)
@AccessTimeout(-1)
public Future<String> addJob(String jobName) {

    // Pretendamos que esta tarea tarda un tiempo
    doSomeHeavyLifting();

    // Retorna nuestro resultado
    return new AsyncResult<String>(jobName);
}

private void doSomeHeavyLifting() {
    try {
        Thread.sleep(SECONDS.toMillis(10));
    } catch (InterruptedException e) {
        Thread.interrupted();
        throw new IllegalStateException(e);
    }
  }
}
----

== Prueba

[source,java]
----
public class JobProcessorTest extends TestCase {

public void test() throws Exception {

    final Context context = EJBContainer.createEJBContainer().getContext();

    final JobProcessor processor = (JobProcessor) context.lookup("java:global/async-methods/JobProcessor");

    final long start = System.nanoTime();

    // Encola mucho trabajo
    final Future<String> red = processor.addJob("red");
    final Future<String> orange = processor.addJob("orange");
    final Future<String> yellow = processor.addJob("yellow");
    final Future<String> green = processor.addJob("green");
    final Future<String> blue = processor.addJob("blue");
    final Future<String> violet = processor.addJob("violet");

    // Espera por el resultado -- 1 minuto de trabajo
    assertEquals("blue", blue.get());
    assertEquals("orange", orange.get());
    assertEquals("green", green.get());
    assertEquals("red", red.get());
    assertEquals("yellow", yellow.get());
    assertEquals("violet", violet.get());

    // Cuanto tiempo tardó?
    final long total = TimeUnit.NANOSECONDS.toSeconds(System.nanoTime() - start);

    // Una ejecución debería tardar entre 9 y 21 seconds
    // El tiempo de ejecución dependen en el número de threads disponibles para la ejecucion asíncrona.
    // En el mejor de los casos son 10s mas un tiempo mínimo
    assertTrue("Expected > 9 but was: " + total, total > 9);
    assertTrue("Expected < 21 but was: " + total, total < 21);

  }
}
----

[source,console]
----
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running org.superbiz.async.JobProcessorTest
INFO - ********************************************************************************
INFO - OpenEJB http://tomee.apache.org/
INFO - Startup: Wed Feb 27 12:46:11 BRT 2019
INFO - Copyright 1999-2018 (C) Apache OpenEJB Project, All Rights Reserved.
INFO - Version: 8.0.0-SNAPSHOT
INFO - Build date: 20190227
INFO - Build time: 04:12
INFO - ********************************************************************************
INFO - openejb.home = /home/soro/git/apache/tomee/examples/async-methods
INFO - openejb.base = /home/soro/git/apache/tomee/examples/async-methods
INFO - Created new singletonService org.apache.openejb.cdi.ThreadSingletonServiceImpl@22f71333
INFO - Succeeded in installing singleton service
INFO - Using 'javax.ejb.embeddable.EJBContainer=true'
INFO - Cannot find the configuration file [conf/openejb.xml].  Will attempt to create one for the beans deployed.
INFO - Configuring Service(id=Default Security Service, type=SecurityService, provider-id=Default Security Service)
INFO - Configuring Service(id=Default Transaction Manager, type=TransactionManager, provider-id=Default Transaction Manager)
INFO - Creating TransactionManager(id=Default Transaction Manager)
INFO - Creating SecurityService(id=Default Security Service)
INFO - Found EjbModule in classpath: /home/soro/git/apache/tomee/examples/async-methods/target/classes
INFO - Beginning load: /home/soro/git/apache/tomee/examples/async-methods/target/classes
INFO - Configuring enterprise application: /home/soro/git/apache/tomee/examples/async-methods
INFO - Auto-deploying ejb JobProcessor: EjbDeployment(deployment-id=JobProcessor)
INFO - Configuring Service(id=Default Singleton Container, type=Container, provider-id=Default Singleton Container)
INFO - Auto-creating a container for bean JobProcessor: Container(type=SINGLETON, id=Default Singleton Container)
INFO - Creating Container(id=Default Singleton Container)
INFO - Configuring Service(id=Default Managed Container, type=Container, provider-id=Default Managed Container)
INFO - Auto-creating a container for bean org.superbiz.async.JobProcessorTest: Container(type=MANAGED, id=Default Managed Container)
INFO - Creating Container(id=Default Managed Container)
INFO - Using directory /tmp for stateful session passivation
INFO - Enterprise application "/home/soro/git/apache/tomee/examples/async-methods" loaded.
INFO - Assembling app: /home/soro/git/apache/tomee/examples/async-methods
INFO - Jndi(name="java:global/async-methods/JobProcessor!org.superbiz.async.JobProcessor")
INFO - Jndi(name="java:global/async-methods/JobProcessor")
INFO - Existing thread singleton service in SystemInstance(): org.apache.openejb.cdi.ThreadSingletonServiceImpl@22f71333
INFO - Some Principal APIs could not be loaded: org.eclipse.microprofile.jwt.JsonWebToken out of org.eclipse.microprofile.jwt.JsonWebToken not found
INFO - OpenWebBeans Container is starting...
INFO - Adding OpenWebBeansPlugin : [CdiPlugin]
INFO - All injection points were validated successfully.
INFO - OpenWebBeans Container has started, it took 316 ms.
INFO - Created Ejb(deployment-id=JobProcessor, ejb-name=JobProcessor, container=Default Singleton Container)
INFO - Started Ejb(deployment-id=JobProcessor, ejb-name=JobProcessor, container=Default Singleton Container)
INFO - Deployed Application(path=/home/soro/git/apache/tomee/examples/async-methods)
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 23.491 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
----

== Como funciona esto detrás de escena

Lo que lo hace trabajar detrás de escena es:

* El `JobProcessor` quien es el llamador ve que no es de hecho una instancia de `JobProcessor`. Por el contrario es una subclase o proxy que tiene todos los métodos sobrescritos. Métodos que deben ser asíncronos son tratados distinto.
* Llamadas a un método asíncrono simplemente retornan un `Runnable` siendo creado que envuelve el método y parámetros que tu pasaste. Este runnable es pasado a un
http://download.oracle.com/javase/6/docs/api/java/util/concurrent/Executor.html[Executor] quien es simplemente una cola de trabajo adjuntada al conjunto de hilos (thread pool).
* Después de añadir el trabajo a la cola, la versión proxeada del método retorna una implementation de `Future` que es enlazada a el `Runnable` quien está ahora esperando en la cola.
* Cuando el `Runnable` finalmente ejecuta el método sobre la instancia _real_ del `JobProcessor`, tomará el valor de retorno y lo asignará dentro del `Future` haciendolo disponible a el que llama.

Importante notar que el objeto `AsyncResult` que `JobProcessor` retorna no es el mismo objeto `Future` que el que llama contiene. Sería genial si el `JobProcessor` real pudiera retornar `String` y que el que la versión de `JobProcessor` del que llama pudiera retornar `Future<String>`, pero no encontramos una manera de hacer eso sin añadir mas complejidad. Entonces el `AsyncResult` es un simple objeto envoltorio. El contenedor sacará el `String`, descartará el `AsyncResult`, entonces pondrá el `String` en el `Future` _real_ que el llamador contiene.

Para obtener status del proceso, simplemente pasa un objeto thread-safe como http://download.oracle.com/javase/6/docs/api/java/util/concurrent/atomic/AtomicInteger.html[AtomicInteger] a el método `@Asynchronous` y has que el código lo actualice periodicamente con el porcentaje completado.

== Ejemplos Relacionados

Para procesamiento asíncrono complejo, la respuesta de JavaEE’s es `@MessageDrivenBean`. Échale una mirada al ejemplo
link:../simple-mdb/README.html[simple-mdb]
