C#のJavaと同等のものasync / await?


151

私は通常のC#開発者ですが、時々Javaでアプリケーションを開発します。C#async / awaitに相当するJavaがあるかどうか疑問に思っていますか?簡単に言うと、Javaで以下に相当するものは何ですか。

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

7
なぜこれがいいのか:ミゲル・デ・イカザによる私たちの世代の「Go Toステートメント」としてのコールバック
andrewdotn 2013年

Javaの現在の解決策は、接頭辞がの実際の値でasyncはなく、代わりにFutureor Observable値を使用することです。
SD

1
同等のものはありません。そしてそれは痛い。これら2つの単純な単語と同じ効果に到達することなく、複雑な回避策とライブラリが必要なもう1つの欠けている機能。
spyro

注目に値するのは、Javaデザイナーが長い間、Javaバイトコードの後方互換性を維持するために、既存の機能に関連するライブラリとSyntatic-sugarの変更のみに対応してきたことです。ジェネリックスは実行時の型情報を格納せず、ランバーはインターフェイスを実装するオブジェクトとして実装されるという事実を参照してください。async / awaitを使用するには、バイトコードを大幅に変更する必要があるため、Javaですぐに表示されるとは思いません。
Philip Couling

回答:


140

いいえ、Javaにはasync / awaitに相当するものはありません。v5より前のC#にもありません。

舞台裏でステートマシンを構築することは、かなり複雑な言語機能です。

Javaの非同期性/並行性に対する言語サポートは比較的少ないですが、java.util.concurrentパッケージにはこれに関する多くの有用なクラスが含まれています。(Task Parallel Libraryとはまったく同じではありませんが、それに最も近いものです。)


15
@ user960567:いいえ、私のポイントは、それが言語機能であることです-ライブラリだけに置くことはできません。少なくともJava 8で同等の予定があるとは思いません。
Jon Skeet、2013年

10
@ user960567:使用しているC#のバージョンと使用している.NETのバージョンを区別する必要があります。async / awaitは言語機能です-C#5で導入されました。はい、Microsoft.Bcl.Asyncを使用して.NET 4をターゲットとするasync / awaitを使用できますが、C#5コンパイラを使用する必要があります。
Jon Skeet、2013年

5
@rozar:いいえ、そうでもない。非同期にはすでに複数のオプションがありますが、RxJavaはC#のように言語を変更しません。私は受信に対しては何もないが、それはC#5の非同期と同じものではありません
ジョンスキート

11
@DtechNet:ええと、非同期のJVM機構はたくさんあります。そうです...それは、非同期をサポートする実際の言語機能があるのとはかなり異なります。(非同期/待機の前にも.NETには多くの非同期がありましたが、非同期/待機により、それを利用するのがはるかに簡単になります。)
Jon Skeet

1
@アーコン:明示的な言語サポートがない限り、答えはまだ正しいと私は主張します。ライブラリの問題だけでスケジューリングが単純になるわけではありません。ここでは、C#コンパイラがステートマシンを構築する方法全体が重要です。
Jon Skeet

41

await(ときに非同期動作の完了追加のコードを実行するために継続を使用client.GetStringAsync(...))。

したがって、最も近い近似として、CompletableFuture<T>Task<TResult>.netに相当するJava 8 )ベースのソリューションを使用して、Httpリクエストを非同期で処理します。

2016年5月25 日、Abril 13でリリースされたAsyncHttpClient v.2に2016年に更新:

したがって、OPの例に相当するJava 8 AccessTheWebAsync()は次のとおりです。

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

この使用法は、非同期HttpクライアントリクエストからCompletableFutureを取得するにはどうすればよいですか? これは、2016年4月13日にリリースされたAsyncHttpClientのバージョン2で提供される新しいAPIに準拠しており、すでにの組み込みサポートを備えていCompletableFuture<T>ます。

AsyncHttpClientのバージョン1を使用した元の回答:

そのために、2つの可能なアプローチがあります。

  • 最初のものはノンブロッキングIOを使用し、私はそれを呼び出しますAccessTheWebAsyncNio。ただし、AsyncCompletionHandler(関数型インターフェイスではなく)抽象クラスであるため、ラムダを引数として渡すことはできません。したがって、匿名クラスの構文のために、冗長性が避けられません。ただし、このソリューションは、指定されたC#の例の実行フローに最も近いものです。

  • 2つ目は少し冗長ではありませんが、応答が完了するまで最終的にスレッドをブロックする新しいタスクを送信f.get()します。

最初のアプローチ、より詳細ですが非ブロッキング:

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

2番目のアプローチは冗長ではありませんが、スレッドをブロックします。

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
実際、これはハッピーフローに相当します。例外の処理、最終的にはその他については説明しません。それらを含めると、コードがはるかに複雑になり、エラーが発生しやすくなります。
ハイム

1
これは継続ではありません。この例は、現在のスレッドを解放して他のものを実行し、応答が到着した後、現在のスレッドでこのメソッドの実行を継続するというasync / awaitの本来の目的を満たしていません。(これは、UIスレッドが応答するため、またはメモリ使用量を削減するために必要です。)この例で行うことは、単純なブロッキングスレッド同期といくつかのコールバックです。
Aleksandr Dubinsky

1
@AleksandrDubinskyコールバックが呼び出し側スレッドで実行されない可能性があることを指摘した場合は、同意します。あなたが正しいです。スレッドをブロックすることに同意しません。25-05-2016に更新された私の更新済みの回答は非ブロッキングです。
ミゲルガンボア

1
....そして、このサンプルは、非同期のものを行うときにC#が書き込みと読み取りを非常に簡単にする理由です。これは、Javaの問題です。
spyro

29

Javaバイトコードを書き換えて非同期/待機をかなりうまくシミュレートするea-asyncを確認してください。readmeによると、「。NET CLRのAsync-Awaitに強く触発されています」


8
誰かが本番環境でそれを使用しますか?
Next DoorのWang氏2016

1
EAはそうしているようですが、制作に適さないものにお金をかけることはないと思います。
BrunoJCM 2017

1
何かにお金を費やしてから、それが生産に適さないと判断するのはごく普通のことです。それが学ぶ唯一の方法です。本番環境ではJavaエージェント設定なしで使用できます。これにより、バーが少し下がります(github.com/electronicarts/ea-async)。
thoredge

16

asyncawaitは構文糖衣です。async and awaitの本質はステートマシンです。コンパイラーは、非同期/待機コードを状態マシンに変換します。

同時に、実際のプロジェクトでasync / awaitを実際に使用できるようにするには、多数の非同期I / Oライブラリー関数を既に用意しておく必要があります。C#の場合、ほとんどの元の同期I / O関数には、代替の非同期バージョンがあります。これらの非同期関数が必要なのは、ほとんどの場合、独自の非同期/待機コードがライブラリの非同期メソッドにまとめられるためです。

C#の非同期バージョンライブラリ関数は、JavaのAsynchronousChannel概念のようなものです。たとえば、AsynchronousFileChannel.readがあります。これは、Futureを返すか、読み取り操作の完了後にコールバックを実行することができます。しかし、まったく同じではありません。すべてのC#非同期関数はタスクを返します(Futureに似ていますが、Futureより強力です)。

Javaが非同期/待機をサポートしているとしましょう。次のようなコードを記述します。

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

次に、コンパイラが元の非同期/待機コードを次のようなものに変換することを想像します。

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

そして、ここに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
合成糖?例外を非同期コードにラップし、非同期コードにループする方法について何か考えがありますか?
Akash Kava

39
クラスも構文糖衣です。コンパイラーは、通常は手動で完全に自動で作成する関数ポインターのすべてのツリーとリストを作成します。これらの関数/メソッドも構文糖です。彼らはあなたが本当のプログラマーであり、手で書く通常のゴットをすべて自動生成します。アセンブラも構文糖です。実際のプログラマーは手動でマシンコードを記述し、それをすべてのターゲットアーキテクチャに手動で移植します。
yeoman

32
ちょっと考えてみると、コンピュータ自体はl4m3 n00bzの単なる構文上の砂糖です。回路基板は大量生産、靴、または食品のように構文上の砂糖であるため、実際のプログラマーはそれらに小さな集積回路を木製ボードにはんだ付けし、それらを金線で接続します。
yeoman

14

言語レベルでのJavaのC#async / awaitに相当するものはありません。ファイバー(別名:協調スレッド、別名:軽量スレッド)として知られる概念は、興味深い代替策になる可能性があります。ファイバーのサポートを提供するJavaライブラリーを見つけることができます。

ファイバーを実装するJavaライブラリ

この記事(Quasarから)を読んで、ファイバーの概要を知ることができます。スレッドとは何か、ファイバーをJVMに実装する方法、Quasar固有のコードがいくつか含まれています。


10
C#の非同期/待機はファイバーではありません。Taskコールバックを登録することにより、Promise(クラス)での継続を使用するコンパイラマジックです。
UltimaWeapon 2016年

1
@UltimaWeaponでは、繊維とは何だと思いますか?
Aleksandr Dubinsky 2018

@AleksandrDubinsky例の1つはgoroutineです。
UltimaWeapon 2018

1
@UltimaWeapon私は説明を探していました。
Aleksandr Dubinsky

@AleksandrDubinsky私はそれを説明するのが面倒です。あなたが本当に知りたいなら、あなたはgoroutineのフードの下でについての記事を検索するかもしれません。
UltimaWeapon 2018

8

すでに述べたように、直接対応するものはありませんが、Javaのバイトコードを変更することで非常に近い近似を作成できます(非同期/待機のような命令と基礎となる継続の実装の両方)。

私は現在、JavaFlow継続ライブラリの上にasync / awaitを実装するプロジェクトに取り組んでいます。https://github.com/vsilaev/java-async-awaitを確認してください

Maven mojoはまだ作成されていませんが、提供されたJavaエージェントを使用してサンプルを実行できます。非同期/待機コードは次のようになります。

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はメソッドに非同期実行可能フラグを付ける注釈であり、await()は継続を使用してCompletableFutureを待機する関数であり、「return asyncResult(someValue)」の呼び出しは関連するCompletableFuture / Continuationを確定するものです

C#と同様に、制御フローは保持され、例外処理は通常の方法で実行できます(順次実行されるコードのように試行/キャッチ)



5

まず、非同期/待機とは何かを理解します。これは、シングルスレッドGUIアプリケーションまたは効率的なサーバーが複数の「ファイバー」または「コルーチン」または「軽量スレッド」を単一のスレッドで実行する方法です。

通常のスレッドを使用することに問題がない場合、Javaの同等の機能はExecutorService.submitおよびFuture.getです。これは、タスクが完了するまでブロックし、結果を返します。その間、他のスレッドが機能します。

ファイバーのようなものの利点が必要な場合は、コンテナー(つまり、GUIイベントループまたはサーバーのHTTPリクエストハンドラー)で、または独自に作成することによるサポートが必要です。

たとえば、サーブレット3.0は非同期処理を提供します。JavaFXが提供するものjavafx.concurrent.Task。ただし、これらには言語機能の優雅さはありません。彼らは通常のコールバックを介して動作します。


2
この回答の最初の段落を再開する記事の引用// //引用の開始Windowsストア、Windowsデスクトップ、Windows Phoneアプリなどのクライアントアプリケーションの場合、非同期の主な利点は応答性です。これらのタイプのアプリは主に非同期を使用して、UIの応答性を維持します。サーバーアプリケーションの場合、非同期の主な利点はスケーラビリティです。 msdn.microsoft.com/en-us/magazine/dn802603.aspx
granadaCoder 2018

3

これをasync / awaitキーワードのように実行できるJavaネイティブの機能はありませんが、本当に必要な場合はCountDownLatchを使用することで実行できます。あなたは可能性があり、その後(少なくともJava7で)この周りを渡すことによって非同期/のawait真似します。これは、非同期呼び出し(通常はハンドラーによってポストされた実行可能ファイル)を実行し、その結果を待つ(カウントダウン)Android単体テストの一般的な方法です。

ただし、テストではなくアプリケーション内でこれを使用することは、私が推奨するものではありません。CountDownLatchは、適切な回数と適切な場所で効果的にカウントダウンすることに依存しているため、これは非常に厄介です。


3

Java async / awaitライブラリを作成してリリースしました。 https://github.com/stofu1234/kamaitachi

このライブラリはコンパイラ拡張を必要とせず、JavaでスタックレスIO処理を実現します。

    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にはasync / awaitに相当するものはありません。取得できる最も近いのは、おそらくGuavaからのListenableFutureとリスナーチェーンですが、ネストレベルが非常に急速に増加するため、複数の非同期呼び出しが含まれるケースを記述するのは依然として非常に面倒です。

JVMの上で別の言語を使用することに問題がなければ、幸いなことに、Scalaにはasync / awaitがあり、これはほぼ同じ構文とセマンティクスを持つ直接のC#async / awaitに相当します。https//github.com/scala/非同期/

この機能にはC#でかなり高度なコンパイラサポートが必要でしたが、Scalaでは非常に強力なマクロシステムのおかげでライブラリとして追加できるため、2.10のような古いバージョンのScalaにも追加できます。さらに、ScalaはJavaとクラス互換であるため、Scalaで非同期コードを記述して、Javaから呼び出すことができます。

Akka Dataflow http://doc.akka.io/docs/akka/2.3-M1/scala/dataflow.htmlと呼ばれる別の同様のプロジェクトもあります。これは、異なる表現を使用しますが、概念的には非常に似ていますが、マクロではなく区切られた継続を使用して実装されます(したがって、2.9のような古いScalaバージョンでも動作します)。


1

Javaには、async / awaitと呼ばれるC#言語機能に直接相当するものはありませんが、async / awaitが解決しようとする問題には別のアプローチがあります。これはプロジェクトLoomと呼ばれ、高スループットの同時実行のための仮想スレッドを提供します。OpenJDKの将来のバージョンで利用可能になる予定です。

このアプローチは、async / awaitが抱えている「カラー関数の問題」も解決します。

同様の機能はGolang(goroutines)にもあります。


0

Javaでasync / awaitと同じ効果をシミュレートするクリーンなコードの直後で、テストなどで完了するまで、呼び出されるスレッドをブロックすることを気にしない場合は、次のようなコードを使用できます。

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

AsynHelper Javaライブラリには、このような非同期呼び出し(および待機)のためのユーティリティクラス/メソッドのセットが含まれています。

一連のメソッド呼び出しまたはコードブロックを非同期で実行する必要がある場合、以下のスニペットのように、便利なヘルパーメソッドAsyncTask .submitTasksが含まれています。

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

すべての非同期コードの実行が完了するまで待機する必要がある場合は、AsyncTask.submitTasksAndWaitバリアントを使用できます。

また、各非同期メソッド呼び出しまたはコードブロックから戻り値を取得する必要がある場合は、AsyncSupplier .submitSuppliersを使用して、メソッドから返された結果サプライヤー配列から結果を取得できます。以下はサンプルスニペットです。

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

各メソッドの戻り値の型が異なる場合は、以下の種類のスニペットを使用してください。

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

非同期メソッド呼び出し/コードブロックの結果は、以下のスニペットのように、同じスレッドまたは異なるスレッドの異なるコードポイントで取得することもできます。

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());
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.