Java equivalente di C # asincrono / attendi?


151

Sono un normale sviluppatore C # ma a volte sviluppo un'applicazione in Java. Mi chiedo se esiste un equivalente Java di C # asincrono / wait? In parole semplici qual è l'equivalente Java di:

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

7
Perché questo sarebbe carino: Callbacks come dichiarazione Go To delle nostre generazioni di Miguel de Icaza.
Andrewdotn,

La soluzione attuale di Java non è gestire i valori effettivi con prefisso async, ma utilizzare invece Futureo Observablevalori.
SD

1
Non c'è equivalente. E fa male. Un'altra caratteristica mancante di cui hai bisogno per complicate soluzioni alternative e librerie per, senza mai raggiungere lo stesso effetto di queste due semplici parole.
spyro

Vale la pena notare che per molto tempo, i progettisti Java hanno cercato di mantenere il bytecode Java retrocompatibile con le sole modifiche alle librerie e Syntatic-Sugar attorno alle funzionalità esistenti. Scopri il fatto che i generici non memorizzano le informazioni sul tipo di runtime e i lambder sono implementati come oggetti che implementano un'interfaccia . async / await richiederebbe modifiche molto grandi al bytecode e quindi non mi aspetto di vederlo presto in Java.
Philip Couling,

Risposte:


141

No, non esiste alcun equivalente di asincrono / wait in Java - o anche in C # prima della v5.

È una funzionalità linguistica abbastanza complessa per costruire una macchina a stati dietro le quinte.

C'è un supporto linguistico relativamente scarso per asincronia / concorrenza in Java, ma il java.util.concurrentpacchetto contiene molte classi utili al riguardo. (Non del tutto equivalente alla Task Parallel Library, ma l'approssimazione più vicina ad essa.)


15
@ user960567: No, il mio punto è che è una funzione del linguaggio , non può essere inserita esclusivamente nelle librerie. Non credo che ci sia almeno un equivalente programmato per Java 8.
Jon Skeet,

10
@ user960567: devi distinguere tra la versione di C # che stai utilizzando e la versione di .NET che stai utilizzando. async / await è una funzionalità del linguaggio : è stata introdotta in C # 5. Sì, puoi usare Microsoft.Bcl.Async per utilizzare async / wait targeting .NET 4, ma devi comunque utilizzare un compilatore C # 5.
Jon Skeet,

5
@rozar: No, non proprio. Esistono già più opzioni per l'asincronia, ma RxJava non cambia la lingua come ha fatto C #. Non ho nulla contro Rx, ma non è la stessa cosa di asincrono in C # 5.
Jon Skeet,

11
@DtechNet: Beh, ci sono molti macchinari JVM che sono asincroni, sì ... è molto diverso dal fatto che ci sono funzioni linguistiche reali che supportano l'asincronia. (C'era molta asincronia in .NET anche prima di async / wait ... ma async / wait rende molto più facile trarne vantaggio.)
Jon Skeet,

1
@Aarkon: direi che a meno che non ci sia supporto esplicito per il linguaggio , la risposta è ancora corretta. Non è solo una questione di librerie che rendono la pianificazione più semplice: l'intero modo in cui il compilatore C # costruisce una macchina a stati è importante qui.
Jon Skeet,

41

Il awaitutilizza una continuazione per eseguire codice aggiuntivo quando al completamento del funzionamento asincrono ( client.GetStringAsync(...)).

Quindi, come approssimazione più ravvicinata, utilizzerei una soluzione CompletableFuture<T>(equivalente a Java 8 equivalente a .net Task<TResult>) per elaborare la richiesta HTTP in modo asincrono.

AGGIORNATO il 25-05-2016 su AsyncHttpClient v.2 rilasciato il 13 aprile 2016:

Quindi Java 8 equivalente all'esempio OP di AccessTheWebAsync()è il seguente:

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

Questo utilizzo è stato preso dalla risposta a Come posso ottenere un CompletableFuture da una richiesta Async Http Client? e che è secondo la nuova API fornita nella versione 2 di AsyncHttpClient rilasciata il 13 aprile 2016, per la quale è già supportato intrinsecamente CompletableFuture<T>.

Risposta originale utilizzando la versione 1 di AsyncHttpClient:

A tal fine, abbiamo due possibili approcci:

  • il primo utilizza IO non bloccante e io lo chiamo AccessTheWebAsyncNio. Tuttavia, poiché AsyncCompletionHandlerè una classe astratta (anziché un'interfaccia funzionale) non possiamo passare un lambda come argomento. Quindi incorre in inevitabile verbosità a causa della sintassi di classi anonime. Tuttavia, questa soluzione è la più vicina al flusso di esecuzione dell'esempio C # dato .

  • il secondo è leggermente meno dettagliato, tuttavia presenterà una nuova attività che alla fine bloccherà un thread f.get()fino al completamento della risposta.

Primo approccio , più dettagliato ma non bloccante:

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

Secondo approccio meno dettagliato ma bloccando 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());
    }
}

1
In realtà, questo è l'equivalente del flusso felice. Non copre la gestione delle eccezioni, finalmente e altre. Includerli renderà il codice molto più complesso e più soggetto a errori.
salta il

1
Questa non è una continuazione. In questo esempio manca il vero scopo di async / await, ovvero rilasciare il thread corrente per eseguire altre cose e quindi continuare l'esecuzione di questo metodo sul thread corrente dopo l'arrivo di una risposta. (Ciò è necessario affinché il thread dell'interfaccia utente sia reattivo o per ridurre l'utilizzo della memoria.) Ciò che fa questo esempio è una semplice sincronizzazione del thread bloccante, oltre ad alcuni callback.
Aleksandr Dubinsky,

1
@AleksandrDubinsky Sono d'accordo con te quando indichi che il callback potrebbe non essere eseguito sul thread del chiamante. Hai ragione. Non sono d'accordo sul blocco di un thread. La mia risposta aggiornata di AGGIORNAMENTO il 25-05-2016 è non bloccante.
Miguel Gamboa,

1
.... e questo esempio è esattamente il motivo per cui C # è molto più semplice da scrivere e leggere quando si fanno cose asincrone. È solo un dolore in Java.
spyro,

29

Dai un'occhiata a ea-async che esegue la riscrittura del bytecode Java per simulare async / attendi abbastanza bene. Secondo il loro readme: "È fortemente ispirato da Async-Await su .NET CLR"


8
Qualcuno lo usa in produzione?
Mr. Wang di Next Door il

1
Sembra che EA lo faccia, non credo che spenderebbero soldi per qualcosa che non è adatto alla produzione.
BrunoJCM,

1
È abbastanza normale spendere soldi per qualcosa e poi decidere che non è adatto alla produzione; questo è l'unico modo per imparare. È possibile utilizzarlo senza le impostazioni dell'agente java in produzione; ciò dovrebbe abbassare leggermente la barra ( github.com/electronicarts/ea-async ).
thoredge

16

asincrono e attendono sono zuccheri sintattici. L'essenza di asincrono e attendere è la macchina a stati. Il compilatore trasformerà il tuo codice asincrono / wait in una macchina a stati.

Allo stesso tempo, affinché Async / attende di essere realmente praticabile in progetti reali, dobbiamo disporre di molte funzioni di libreria I / O Async già attive. Per C #, la maggior parte delle funzioni di I / O sincronizzate originali ha una versione alternativa di Async. Il motivo per cui abbiamo bisogno di queste funzioni Async è perché nella maggior parte dei casi, il tuo codice asincrono / attesa si riduce a qualche metodo Async della libreria.

La libreria della versione di Async funziona in C # è un po 'come il concetto AsynchronousChannel in Java. Ad esempio, abbiamo AsynchronousFileChannel.read che può restituire un Future o eseguire un callback al termine dell'operazione di lettura. Ma non è esattamente lo stesso. Tutte le funzioni Asincrone C # restituiscono Attività (simili a Future ma più potenti di Future).

Quindi supponiamo che Java supporti async / await e scriviamo un codice come questo:

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

Quindi immagino che il compilatore trasformi il codice originale asincrono / wait in qualcosa del genere:

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

Ed ecco l'implementazione per 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
Zucchero sintatico? Hai idea di come racchiudere le eccezioni nel codice asincrono e fare il giro del codice asincrono?
Akash Kava,

39
Anche le lezioni sono zucchero sintattico. Il compilatore crea tutti gli alberi e gli elenchi di puntatori a funzioni che normalmente scriveresti manualmente per te in modo completamente automatico. Queste funzioni / metodi sono anche zucchero sintattico. Generano automaticamente tutte le foto che normalmente, essendo un vero programmatore, scrivono a mano. Anche l'assemblatore è zucchero sintattico. I veri programmatori scrivono manualmente il codice macchina e lo portano manualmente a tutte le architetture di destinazione.
yeoman,

32
Pensando a ciò, i computer stessi sono solo zucchero sintattico per l4m3 n00bz. I veri programmatori saldano piccoli circuiti integrati su una tavola di legno e li collegano con filo d'oro perché i circuiti stampati sono zucchero sintattico, proprio come la produzione di massa, le scarpe o il cibo.
yeoman,

14

Non esiste un equivalente di C # asincrono / wait in Java a livello di lingua. Un concetto noto come Fibre alias fili cooperative alias fili leggeri potrebbe essere un'alternativa interessante. È possibile trovare librerie Java che forniscono supporto per le fibre.

Librerie Java che implementano Fibre

Puoi leggere questo articolo (da Quasar) per una bella introduzione alle fibre. Copre cosa sono i thread, come le fibre possono essere implementate sulla JVM e ha un codice specifico di Quasar.


10
async / waitit in C # non è una fibra. È solo compilatore magico che usa la continuazione su Promise (la Taskclasse) registrando un callback.
UltimaWeapon,

1
@UltimaWeapon Quindi cosa consideri fibre?
Aleksandr Dubinsky,

@AleksandrDubinsky Uno degli esempi è goroutine.
UltimaWeapon,

1
@UltimaWeapon Stavo cercando una spiegazione.
Aleksandr Dubinsky,

@AleksandrDubinsky Sono pigro per spiegarlo. Se vuoi davvero sapere puoi cercare l'articolo sotto il cofano di goroutine.
UltimaWeapon,

8

Come è stato menzionato, non esiste un equivalente diretto, ma è possibile creare un'approssimazione molto stretta con modifiche del codice bytecode Java (sia per istruzioni asincrone / di attesa che per l'implementazione di continuazioni sottostanti).

Attualmente sto lavorando a un progetto che implementa async / wait in cima alla libreria di continuazione JavaFlow , per favore controlla https://github.com/vsilaev/java-async-await

Non è stato ancora creato Maven mojo, ma è possibile eseguire esempi con l'agent Java fornito. Ecco come appare il codice asincrono / 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 è l'annotazione che contrassegna un metodo come eseguibile in modo asincrono, waitit () è una funzione che attende CompletableFuture usando le continuazioni e una chiamata a "return asyncResult (someValue)" è ciò che finalizza associato CompletableFuture / Continuation

Come con C #, il flusso di controllo viene preservato e la gestione delle eccezioni può essere eseguita in modo regolare (provare / catturare come nel codice eseguito in sequenza)


6

Java stesso non ha caratteristiche equivalenti, ma esistono librerie di terze parti che offrono funzionalità simili, ad esempio Kilim .


3
Non credo che questa libreria abbia qualcosa a che fare con ciò che fa async / wait.
Natan,

5

Innanzitutto, capire cos'è async / wait. È un modo per un'applicazione GUI a thread singolo o un server efficiente di eseguire più "fibre" o "co-routine" o "thread leggeri" su un singolo thread.

Se stai bene usando i normali thread, allora l'equivalente Java è ExecutorService.submite Future.get. Questo si bloccherà fino al completamento dell'attività e restituirà il risultato. Nel frattempo, altri thread possono funzionare.

Se si desidera il vantaggio di qualcosa di simile alle fibre, è necessario il supporto nel contenitore (intendo nel ciclo di eventi della GUI o nel gestore di richieste HTTP del server) o scrivendo il proprio.

Ad esempio, Servlet 3.0 offre l'elaborazione asincrona. Offerte di JavaFX javafx.concurrent.Task. Questi però non hanno l'eleganza delle caratteristiche del linguaggio. Funzionano attraverso normali callback.


2
Ecco una citazione dell'articolo che riavvia il primo paragrafo di questa risposta // inizio citazione Per le applicazioni client, come Windows Store, desktop Windows e app Windows Phone, il vantaggio principale dell'asincronismo è la reattività. Questi tipi di app utilizzano principalmente l'asincrono per mantenere reattiva l'interfaccia utente. Per le applicazioni server, il vantaggio principale di asincrono è la scalabilità. msdn.microsoft.com/en-us/magazine/dn802603.aspx
granadaCoder

3

Non c'è niente di nativo in Java che ti permetta di fare questo come le parole chiave asincroni / wait, ma quello che puoi fare se vuoi davvero è usare un CountDownLatch . Si potrebbe quindi imitare asincrone / attendono passando questo intorno (almeno in Java7). Questa è una pratica comune nel test di unità Android in cui dobbiamo effettuare una chiamata asincrona (di solito un runnable pubblicato da un gestore), e quindi attendere il risultato (conto alla rovescia).

Usarlo tuttavia all'interno dell'applicazione invece del test NON è ciò che sto raccomandando. Sarebbe estremamente scadente poiché CountDownLatch dipende dal fatto che tu abbia effettivamente il conto alla rovescia per il numero giusto di volte e nei posti giusti.


3

Realizzo e rilascio una libreria Java async / waitit. https://github.com/stofu1234/kamaitachi

Questa libreria non ha bisogno dell'estensione del compilatore e realizza l'elaborazione IO stackless in 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

Java purtroppo non ha equivalenti di async / waitit. Il più vicino che puoi ottenere è probabilmente con ListenableFuture di Guava e il concatenamento di ascoltatori, ma sarebbe comunque molto complicato scrivere per casi che coinvolgono più chiamate asincrone, poiché il livello di nidificazione aumenterebbe molto rapidamente.

Se stai bene usando una lingua diversa su JVM, fortunatamente c'è asincrono / wait in Scala che è un asincrono / attesa C # diretto equivalente con una sintassi e semantica quasi identiche: https://github.com/scala/ async /

Si noti che sebbene questa funzionalità necessitasse di un supporto del compilatore piuttosto avanzato in C #, in Scala potrebbe essere aggiunta come libreria grazie a un sistema macro molto potente in Scala e quindi può essere aggiunta anche a versioni precedenti di Scala come 2.10. Inoltre Scala è compatibile con la classe con Java, quindi è possibile scrivere il codice asincrono in Scala e quindi chiamarlo da Java.

Esiste anche un altro progetto simile chiamato Akka Dataflow http://doc.akka.io/docs/akka/2.3-M1/scala/dataflow.html che utilizza una formulazione diversa ma concettualmente è molto simile, tuttavia implementato usando continuazioni delimitate, non macro (quindi funziona con versioni Scala ancora più vecchie come 2.9).


1

Java non ha un equivalente diretto della funzione del linguaggio C # chiamata async / await, tuttavia esiste un approccio diverso al problema che async / wait tenta di risolvere. Si chiama Project Loom , che fornirà thread virtuali per la concorrenza ad alto rendimento. Sarà disponibile in alcune versioni future di OpenJDK.

Questo approccio risolve anche il " problema della funzione colorata " che asincrono / attende.

Funzionalità simili si possono trovare anche in Golang ( goroutine ).


0

Se stai cercando un codice pulito che simuli lo stesso effetto di asincronizzazione / wait in java e non ti dispiace bloccare il thread su cui viene chiamato fino al termine, come in un test, puoi usare qualcosa come questo codice:

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

La libreria AsynHelper Java include una serie di classi / metodi di utilità per tali chiamate asincrone (e attendi).

Se si desidera eseguire una serie di chiamate di metodo o blocchi di codice in modo asincrono, include un utile metodo di supporto AsyncTask .submitTasks come nello snippet di seguito.

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

Se si desidera attendere il completamento dell'esecuzione di tutti i codici asincroni, è possibile utilizzare la variante AsyncTask.submitTasksAndWait .

Inoltre, se si desidera ottenere un valore di ritorno da ciascuna chiamata di metodo asincrona o blocco di codice, è possibile utilizzare AsyncSupplier .submitSuppliers in modo che il risultato possa essere ottenuto dall'array dei fornitori di risultati restituito dal metodo. Di seguito è riportato lo snippet di esempio:

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 il tipo restituito di ciascun metodo differisce, utilizza il tipo di frammento di seguito.

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

Il risultato del metodo asincrono di chiamate / blocchi di codice può anche essere ottenuto in un punto di codice diverso nello stesso thread o in un thread diverso rispetto allo snippet seguente.

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());
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.