Equivalent Java de C # async / await?

151

Je suis un développeur C # normal mais je développe parfois des applications en Java. Je me demande s'il existe un équivalent Java de C # async / await? En termes simples, quel est l'équivalent java de:

async Task<int> AccessTheWebAsync()
{ 
    HttpClient client = new HttpClient();
    var urlContents = await client.GetStringAsync("http://msdn.microsoft.com");
    return urlContents.Length;
}
user960567
la source
7
Pourquoi ce serait bien: Des rappels en tant que déclaration Go To de nos générations par Miguel de Icaza.
andrewdotn
La solution actuelle de Java consiste à ne pas traiter les valeurs réelles préfixées par async, mais à utiliser Futureou à la Observableplace.
SD
1
Il n'y a pas d'équivalent. Et ça fait mal. Une autre fonctionnalité manquante pour laquelle vous avez besoin de solutions de contournement compliquées et de bibliothèques, sans jamais atteindre le même effet que ces deux mots simples.
spyro
Il est intéressant de noter que pendant longtemps, les concepteurs Java ont essayé de garder le bytecode Java rétrocompatible avec uniquement des modifications apportées aux bibliothèques et à Syntatic-sugar autour des fonctionnalités existantes. Voyez le fait que les génériques ne stockent pas les informations de type d'exécution et que les lambders sont implémentés en tant qu'objet implémentant une interface . async / await nécessiterait des modifications très importantes du bytecode pour être possible et donc je ne m'attendrais pas à le voir dans java de si tôt.
Philip Couling le

Réponses:

141

Non, il n'y a pas d'équivalent async / await en Java - ni même en C # avant la v5.

C'est une fonctionnalité de langage assez complexe pour construire une machine à états dans les coulisses.

Il y a relativement peu de prise en charge du langage pour l'asynchronie / concurrence en Java, mais le java.util.concurrentpackage contient beaucoup de classes utiles autour de cela. (Pas tout à fait équivalent à la bibliothèque parallèle de tâches, mais l'approximation la plus proche de celle-ci.)

Jon Skeet
la source
15
@ user960567: Non, ce que je veux dire, c'est que c'est une fonctionnalité de langage - elle ne peut pas être placée uniquement dans les bibliothèques. Je ne pense pas qu'il y ait au moins un équivalent prévu pour Java 8.
Jon Skeet
10
@ user960567: Vous devez faire la distinction entre la version de C # que vous utilisez et la version de .NET que vous utilisez. async / await est une fonctionnalité de langage - elle a été introduite dans C # 5. Oui, vous pouvez utiliser Microsoft.Bcl.Async pour utiliser async / await ciblant .NET 4, mais vous devez toujours utiliser un compilateur C # 5.
Jon Skeet
5
@rozar: Non, pas vraiment. Il existe déjà plusieurs options pour l'asynchronie - mais RxJava ne change pas la langue de la même manière que C #. Je n'ai rien contre Rx, mais ce n'est pas la même chose que l'async en C # 5.
Jon Skeet
11
@DtechNet: Eh bien, il y a beaucoup de machines JVM qui sont asynchrones, oui ... c'est très différent des fonctionnalités de langage réelles prenant en charge l'asynchronie. (Il y avait beaucoup d'asynchronie dans .NET avant async / await, aussi ... mais async / await rend beaucoup plus facile de profiter de cela.)
Jon Skeet
1
@Aarkon: Je dirais qu'à moins d'un support de langage explicite , la réponse est toujours correcte. Ce n'est pas seulement une question de bibliothèques qui simplifient la planification - la manière dont le compilateur C # construit une machine à états est importante ici.
Jon Skeet du
41

Le awaitutilise une continuation pour exécuter du code supplémentaire lorsque l'opération asynchrone se termine ( client.GetStringAsync(...)).

Donc, comme approximation la plus proche, j'utiliserais une solution basée sur CompletableFuture<T>(l'équivalent Java 8 de .net Task<TResult>) pour traiter la requête Http de manière asynchrone.

MISE À JOUR le 25-05-2016 vers AsyncHttpClient v.2 publié le 13 avril 2016:

Donc, l'équivalent Java 8 de l'exemple OP de AccessTheWebAsync()est le suivant:

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

Cette utilisation a été tirée de la réponse à Comment obtenir un CompletableFuture à partir d'une demande Async Http Client? et qui est selon la nouvelle API fournie dans la version 2 d' AsyncHttpClient publiée le 13 avril 2016, qui a déjà un support intrinsèque pourCompletableFuture<T> .

Réponse originale utilisant la version 1 d'AsyncHttpClient:

À cette fin, nous avons deux approches possibles:

  • le premier utilise des E / S non bloquantes et je l'appelle AccessTheWebAsyncNio. Pourtant, comme le AsyncCompletionHandlerest une classe abstraite (au lieu d'une interface fonctionnelle), nous ne pouvons pas passer un lambda comme argument. Cela entraîne donc une verbosité inévitable en raison de la syntaxe des classes anonymes. Cependant, cette solution est la plus proche du flux d'exécution de l'exemple C # donné .

  • le second est légèrement moins verbeux mais il soumettra une nouvelle tâche qui finira par bloquer un fil f.get()jusqu'à ce que la réponse soit complète.

Première approche , plus verbeuse mais non bloquante:

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

Deuxième approche moins verbeuse mais bloquant un fil:

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
la source
1
En fait, c'est l'équivalent du happy flow. Il ne couvre pas la gestion des exceptions, enfin et autres. Les inclure rendra le code beaucoup plus complexe et plus sujet aux erreurs.
haimb
1
Ce n'est pas une continuation. Cet exemple manque le véritable objectif de async / await, qui est de libérer le thread actuel pour exécuter d'autres choses, puis de continuer l'exécution de cette méthode sur le thread actuel après l'arrivée d'une réponse. (Ceci est soit nécessaire pour que le thread d'interface utilisateur soit réactif, soit pour réduire l'utilisation de la mémoire.) Cet exemple fait est une synchronisation de thread bloquant simple, ainsi que quelques rappels.
Aleksandr Dubinsky
1
@AleksandrDubinsky Je suis d'accord avec vous lorsque vous dites que le rappel peut ne pas s'exécuter sur le fil de l'appelant. Vous avez raison. Je ne suis pas d'accord sur le blocage d'un fil. Ma réponse mise à jour de MISE À JOUR le 25-05-2016 est non bloquante.
Miguel Gamboa
1
.... et cet exemple est exactement la raison pour laquelle C # est tellement plus simple à écrire et à lire lors de tâches asynchrones. C'est juste une douleur à Java.
spyro
29

Découvrez ea-async qui effectue la réécriture de bytecode Java pour simuler async / wait assez bien. Selon leur readme: "Il est fortement inspiré par Async-Await sur le .NET CLR"

Hafthor
la source
8
Quelqu'un l'utilise-t-il en production?
Mr.Wang de Next Door
1
Il semble que EA le fasse, je ne pense pas qu'ils dépenseraient de l'argent pour quelque chose qui ne convient pas à la production.
BrunoJCM
1
Il est assez normal de dépenser de l'argent pour quelque chose et de décider ensuite qu'il ne convient pas à la production; c'est la seule façon d'apprendre. Il est possible de l'utiliser sans paramètres d'agent Java en production; cela devrait abaisser un peu la barre ( github.com/electronicarts/ea-async ).
thoredge
16

async et await sont des sucres syntaxiques. L'essence de l'async et de l'attente est la machine à états. Le compilateur transformera votre code asynchrone / en attente en une machine à états.

Dans le même temps, pour qu'async / await soit vraiment réalisable dans de vrais projets, nous avons besoin de nombreuses fonctions de bibliothèque d'E / S Async déjà en place. Pour C #, la plupart des fonctions d'E / S synchronisées d'origine ont une autre version Async. La raison pour laquelle nous avons besoin de ces fonctions Async est que dans la plupart des cas, votre propre code async / await se résumera à une méthode Async de bibliothèque.

Les fonctions de la bibliothèque de version Async en C # sont un peu comme le concept AsynchronousChannel en Java. Par exemple, nous avons AsynchronousFileChannel.read qui peut renvoyer un Future ou exécuter un rappel une fois l'opération de lecture terminée. Mais ce n'est pas exactement la même chose. Toutes les fonctions C # Async renvoient des tâches (similaires à Future mais plus puissantes que Future).

Alors disons que Java prend en charge async / await, et nous écrivons un code comme celui-ci:

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

Ensuite, j'imagine que le compilateur transformera le code async / wait original en quelque chose comme ceci:

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

Et voici l'implémentation pour 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
la source
15
Sucre syntatique? Avez-vous une idée sur la façon d'envelopper des exceptions autour du code asynchrone et des boucles autour du code asynchrone?
Akash Kava
39
Les classes sont également du sucre syntaxique. Le compilateur crée tous les arbres et listes de pointeurs de fonction que vous écririez normalement à la main pour vous de manière entièrement automatique. Ces fonctions / méthodes sont également du sucre syntaxique. Ils génèrent automatiquement tous les gotos que vous voudriez normalement, étant un vrai programmeur, écrire à la main. L'assembleur est aussi du sucre syntaxique. Les vrais programmeurs écrivent manuellement le code machine et le portent manuellement sur toutes les architectures cibles.
yeoman
32
En y réfléchissant bien, les ordinateurs eux-mêmes ne sont que du sucre syntaxique pour l4m3 n00bz. Les vrais programmeurs les soudent de minuscules circuits intégrés à une planche de bois et les connectent avec du fil d'or parce que les circuits imprimés sont du sucre syntaxique, tout comme la production de masse, les chaussures ou la nourriture.
yeoman
14

Il n'y a pas d'équivalent de C # async / await en Java au niveau du langage. Un concept connu sous le nom de Fibres aka threads coopératifs aka threads légers pourrait être une alternative intéressante. Vous pouvez trouver des bibliothèques Java prenant en charge les fibres.

Bibliothèques Java implémentant Fibers

Vous pouvez lire cet article (de Quasar) pour une belle introduction aux fibres. Il couvre ce que sont les threads, comment les fibres peuvent être implémentées sur la JVM et contient du code spécifique à Quasar.

FabienB
la source
10
async / await en C # n'est pas une fibre. C'est juste la magie du compilateur qui utilise la continuation sur Promise (la Taskclasse) en enregistrant un rappel.
UltimaWeapon
1
@UltimaWeapon Alors, que considérez-vous comme des fibres?
Aleksandr Dubinsky
@AleksandrDubinsky Un des exemples est la goroutine.
UltimaWeapon
1
@UltimaWeapon Je cherchais une explication.
Aleksandr Dubinsky
@AleksandrDubinsky Je suis paresseux pour l'expliquer. Si vous voulez vraiment savoir, vous pouvez rechercher l'article sur sous le capot de la goroutine.
UltimaWeapon
8

Comme il a été mentionné, il n'y a pas d'équivalent direct, mais une approximation très proche pourrait être créée avec des modifications de bytecode Java (pour les instructions de type async / await et l'implémentation des continuations sous-jacentes).

Je travaille actuellement sur un projet qui implémente async / await en plus de la bibliothèque de continuation JavaFlow , veuillez vérifier https://github.com/vsilaev/java-async-await

Aucun mojo Maven n'est encore créé, mais vous pouvez exécuter des exemples avec l'agent Java fourni. Voici à quoi ressemble le code async / await:

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 est l'annotation qui marque une méthode comme exécutable de manière asynchrone, await () est une fonction qui attend sur CompletableFuture en utilisant des continuations et un appel à "return asyncResult (someValue)" est ce qui finalise CompletableFuture / Continuation associé

Comme avec C #, le flux de contrôle est préservé et la gestion des exceptions peut être effectuée de manière régulière (try / catch comme dans le code exécuté séquentiellement)

Valery Silaev
la source
6

Java lui-même n'a pas de fonctionnalités équivalentes, mais il existe des bibliothèques tierces qui offrent des fonctionnalités similaires, par exemple Kilim .

Alexei Kaigorodov
la source
3
Je ne pense pas que cette bibliothèque ait quoi que ce soit à voir avec ce que fait async / await.
Natan
5

Tout d'abord, comprenez ce qu'est async / await. C'est un moyen pour une application GUI à un seul thread ou un serveur efficace d'exécuter plusieurs "fibres" ou "co-routines" ou "threads légers" sur un seul thread.

Si vous êtes d'accord avec l'utilisation de threads ordinaires, l'équivalent Java est ExecutorService.submitet Future.get. Cela bloquera jusqu'à la fin de la tâche et retournera le résultat. Pendant ce temps, d'autres threads peuvent fonctionner.

Si vous voulez bénéficier de quelque chose comme les fibres, vous avez besoin de support dans le conteneur (je veux dire dans la boucle d'événement GUI ou dans le gestionnaire de requêtes HTTP du serveur), ou en écrivant le vôtre.

Par exemple, Servlet 3.0 offre un traitement asynchrone. Offres JavaFX javafx.concurrent.Task. Cependant, ceux-ci n'ont pas l'élégance des fonctionnalités du langage. Ils fonctionnent grâce à des rappels ordinaires.

Aleksandr Dubinsky
la source
2
Voici une citation d'article relançant le premier paragraphe de cette réponse // démarrer la citation Pour les applications clientes, telles que le Windows Store, le bureau Windows et les applications Windows Phone, le principal avantage de l'async est la réactivité. Ces types d'applications utilisent principalement async pour garder l'interface utilisateur réactive. Pour les applications serveur, le principal avantage de l'async est l'évolutivité. msdn.microsoft.com/en-us/magazine/dn802603.aspx
granadaCoder
3

Il n'y a rien de natif à Java qui vous permet de faire cela comme les mots-clés async / await, mais ce que vous pouvez faire si vous le souhaitez vraiment, c'est utiliser un CountDownLatch . Vous pouvez alors imiter async / await en le passant (au moins en Java7). C'est une pratique courante dans les tests unitaires Android où nous devons faire un appel asynchrone (généralement un exécutable posté par un gestionnaire), puis attendre le résultat (compte à rebours).

L'utilisation de cela dans votre application, par opposition à votre test, n'est PAS ce que je recommande. Ce serait extrêmement de mauvaise qualité car CountDownLatch dépend de votre compte à rebours le bon nombre de fois et aux bons endroits.

Stevebot
la source
3

J'ai créé et publié la bibliothèque Java async / await. https://github.com/stofu1234/kamaitachi

Cette bibliothèque n'a pas besoin d'extension de compilateur et réalise un traitement d'E / S sans pile 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
la source
1

Java n'a malheureusement pas d'équivalent async / await. Le plus proche que vous puissiez obtenir est probablement avec ListenableFuture de Guava et le chaînage d'auditeurs, mais il serait toujours très fastidieux d'écrire pour les cas impliquant plusieurs appels asynchrones, car le niveau d'imbrication augmenterait très rapidement.

Si vous êtes d'accord pour utiliser un autre langage en plus de JVM, heureusement, il y a async / await dans Scala qui est un équivalent direct C # async / await avec une syntaxe et une sémantique presque identiques: https://github.com/scala/ asynchrone /

Notez que bien que cette fonctionnalité ait besoin d'un support de compilateur assez avancé en C #, dans Scala, elle pourrait être ajoutée en tant que bibliothèque grâce à un système de macros très puissant dans Scala et peut donc être ajoutée même aux anciennes versions de Scala comme 2.10. De plus, Scala est compatible avec les classes Java, vous pouvez donc écrire le code async dans Scala, puis l'appeler depuis Java.

Il existe également un autre projet similaire appelé Akka Dataflow http://doc.akka.io/docs/akka/2.3-M1/scala/dataflow.html qui utilise un libellé différent mais est conceptuellement très similaire, mais mis en œuvre en utilisant des continuations délimitées, pas des macros (donc cela fonctionne avec les versions Scala encore plus anciennes comme 2.9).

Piotr Kołaczkowski
la source
1

Java n'a pas d'équivalent direct de la fonctionnalité du langage C # appelée async / await, mais il existe une approche différente du problème qu'async / await tente de résoudre. C'est ce qu'on appelle le projet Loom , qui fournira des threads virtuels pour une concurrence à haut débit. Il sera disponible dans une future version d'OpenJDK.

Cette approche résout également le " problème de fonction colorée " qu'async / await a.

Une caractéristique similaire peut également être trouvée à Golang ( goroutines ).

Martin Obrátil
la source
0

Si vous recherchez juste un code propre qui simule le même effet que async / await en java et que cela ne vous dérange pas de bloquer le thread sur lequel il est appelé jusqu'à ce qu'il soit terminé, comme dans un test, vous pouvez utiliser quelque chose comme ce code:

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();
                }
            });
        }
    });
Pastille
la source
0

La bibliothèque Java AsynHelper comprend un ensemble de classes / méthodes utilitaires pour de tels appels asynchrones (et attendre).

Si vous souhaitez exécuter un ensemble d'appels de méthode ou de blocs de code de manière asynchrone, il inclut une méthode d'assistance utile AsyncTask .submitTasks comme dans l'extrait de code ci-dessous.

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

Si vous souhaitez attendre que tous les codes asynchrones soient exécutés, la variante AsyncTask.submitTasksAndWait peut être utilisée.

De même, si vous souhaitez obtenir une valeur de retour à partir de chaque appel de méthode asynchrone ou bloc de code, AsyncSupplier .submitSuppliers peut être utilisé afin que le résultat puisse être ensuite obtenu à partir du tableau des fournisseurs de résultats renvoyé par la méthode. Voici un exemple d'extrait de code:

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 le type de retour de chaque méthode diffère, utilisez le type d'extrait de code ci-dessous.

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

Le résultat des appels / blocs de code de méthode asynchrone peut également être obtenu à un point de code différent dans le même thread ou dans un thread différent comme dans l'extrait de code ci-dessous.

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
la source