web-dev-qa-db-fra.com

Équivalent Java de C # async/wait?

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/wait? 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;
}
110
user960567

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

Construire une machine à états en coulisse est une fonctionnalité de langage assez complexe.

La prise en charge de l'asynchronie et de la simultanéité par language est relativement faible en Java, mais le paquetage Java.util.concurrent contient de nombreuses classes utiles autour de cette question. (Pas tout à fait équivalent à la bibliothèque de tâches parallèles, mais à son approximation la plus proche.)

112
Jon Skeet

La await utilise une continuation pour exécuter du code supplémentaire à la fin de l'opération asynchrone (client.GetStringAsync(...)). 

Donc, à titre d'approximation la plus proche, j'utiliserais une solution basée sur CompletableFuture<T> (l'équivalent Java 8 de .net Task<TResult>) pour traiter la demande HTTP de manière asynchrone. 

Mis à jour le 25-05-2016 à AsyncHttpClient v.2 publié le 13 avril 2016:

Ainsi, 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 provient de la réponse à Comment obtenir une requête CompletableFuture à partir d'une demande client async HTTP? et qui est conforme à la nouvelle API fournie dans la version 2 de AsyncHttpClient publiée le 13 avril 2016, qui possède déjà un support intrinsèque pour CompletableFuture<T>.

Réponse originale utilisant la version 1 de AsyncHttpClient:

À cette fin, nous avons deux approches possibles: 

  • le premier utilise non-bloquant IO et je l’appelle AccessTheWebAsyncNio. Cependant, étant donné que AsyncCompletionHandler est une classe abstraite (au lieu d'une interface fonctionnelle), nous ne pouvons pas passer un lambda en tant qu'argument. Cela implique donc inévitablement une verbosité due à 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 détaillé, mais il soumettra une nouvelle tâche qui bloquera finalement un thread sur f.get() jusqu'à ce que la réponse soit complète.

Première approche, plus détaillée 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 bavarde mais bloquant un 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());
    }
}
32
Miguel Gamboa

Découvrez ea-async qui effectue la réécriture du bytecode Java pour simuler async/attend plutôt bien. Selon leur readme: "Il est fortement inspiré d'Async-Await sur le .NET CLR"

17
Hafthor

async et wait sont des sucres syntactiques. L'essence d'async et wait est la machine à états. Le compilateur transformera votre code async/wait en une machine à états.

Dans le même temps, pour que async/wait soit vraiment réalisable dans des projets réels, nous avons besoin de beaucoup de fonctions de bibliothèque Async I/O déjà en place. Pour C #, la plupart des fonctions d'E/S synchronisées d'origine ont une version alternative asynchrone. Nous avons besoin de ces fonctions asynchrones parce que, dans la plupart des cas, votre propre code async/wait se résumera à une méthode Async de bibliothèque. 

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

Disons que Java supporte async/wait, et nous écrivons du 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 va transformer le code original async/wait 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);
    }
}
15
caisx25

C # async/wait est comparable à un concept appelé Fibres aka threads coopératifs aka threads légers. Il n’existe pas d’équivalent de C # async/wait en Java au niveau du langage, mais vous pouvez trouver des bibliothèques prenant en charge les fibres.

Bibliothèques Java implémentant des fibres

Vous pouvez lire cet article (de Quasar) pour une bonne 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.

13
FabienB

Comme il a été mentionné, il n’existe pas d’équivalent direct, mais des modifications très proches pourraient être créées à l’aide de modifications de bytecodes Java (pour les instructions asynchrones/similaires et l’implémentation des suites sous-jacentes).

Je travaille actuellement sur un projet qui implémente async/wait en plus de suite de JavaFlow library, veuillez vérifier https://github.com/vsilaev/Java-async-await

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

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, wait () est une fonction qui attend CompletableFuture en utilisant des continuations et un appel à "return asyncResult (someValue)" qui finalise CompletableFuture/Continuation associé.

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

6
Valery Silaev

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

6

Il n'y a rien de natif à Java qui vous permette de faire cela comme des mots-clés async/wait, mais ce que vous pouvez faire si vous voulez vraiment utiliser un CountDownLatch . Vous pourriez imiter ensuite async// en faisant circuler ceci (au moins en Java7). Ceci est une pratique courante dans les tests unitaires Android où nous devons effectuer un appel async (généralement un exécutable publié par un gestionnaire), puis attendre le résultat (compte à rebours). 

Cependant, utiliser ceci dans votre application, par opposition à votre test, correspond à ce que je recommande PAS. Ce serait extrêmement décevant, car CountDownLatch dépend de votre capacité à compter le bon nombre de fois et au bon endroit. 

3
stevebot

Tout d’abord, comprenez ce que async/wait est. C'est une façon pour une application à interface graphique à un seul thread ou un serveur efficace d'exécuter plusieurs "fibres", "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.submit et Future.get. Cela bloque jusqu'à la fin de la tâche et renvoie le résultat. Pendant ce temps, d'autres threads peuvent fonctionner.

Si vous voulez bénéficier de quelque chose comme des fibres, vous avez besoin d’une prise en charge dans le conteneur (je veux dire dans la boucle d’événements de l’interface graphique ou dans le gestionnaire de requêtes HTTP du serveur), ou vous écrivez vous-même. Par exemple, Servlet 3.0 offre un traitement asynchrone. JavaFX offre javafx.concurrent.Task. Ceux-ci n'ont cependant pas l'élégance des fonctionnalités de la langue. Ils travaillent à travers des rappels ordinaires.

3
Aleksandr Dubinsky

Java n'a malheureusement pas d'équivalent async/wait. ListenableFuture de Guava et le chaînage des auditeurs constituent votre meilleur moyen de vous rapprocher, mais il serait toujours très fastidieux d’écrire pour les cas impliquant plusieurs appels asynchrones, car le niveau de nidification augmenterait très rapidement.

Si vous acceptez d'utiliser un langage différent de la machine virtuelle Java, heureusement, il existe async/wait dans Scala, qui est un équivalent direct de C # async/wait avec une syntaxe et une sémantique presque identiques: https: // github .com/scala/async/

Notez que bien que cette fonctionnalité nécessite un support de compilateur assez avancé en C #, elle peut être ajoutée à Scala 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 telles que 2.10. De plus, Scala est compatible avec Java, ce qui vous permet d'écrire le code asynchrone dans Scala, puis de l'appeler à partir de 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 dont le concept est très similaire, bien que implémenté en utilisant délimité continuations, pas de macros (donc cela fonctionne avec les versions les plus anciennes de Scala comme 2.9).

1
Piotr Kołaczkowski

Je fais et libère Java async/wait library . https://github.com/stofu1234/kamaitachi

Cette bibliothèque n'a pas besoin de l'extension du compilateur et réalise un traitement sans pile IO 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);
            }
            );
    }
1
stofu

AsynHelper La bibliothèque Java inclut un ensemble de classes/méthodes utilitaires pour ces appels asynchrones (et wait) .

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 utile. AsyncTask . SubmitTasks comme dans l'extrait 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 terminés, le AsyncTask.submitTasksAndWait la variante 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, vous pouvez utiliser le fichier AsyncSupplier . SubmitSuppliers afin d'obtenir le résultat obtenu à partir du tableau fournisseurs retourné par la méthode. Ci-dessous l'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 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 de méthode asynchrones/des blocs de code peut également être obtenu à un point de code différent dans le même thread ou dans un thread différent de celui de 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());
0
Loganathan

Si vous recherchez un code vierge qui simule le même effet que async/wait en Java et que le blocage du thread sur lequel il est appelé jusqu'à ce qu'il soit terminé, par exemple 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();
                }
            });
        }
    });
0
Pellet