私は普通の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;
}
いいえ、Javaにはasync/awaitに相当するものはありません。v5より前のC#にもありません。
舞台裏でステートマシンを構築するのはかなり複雑な言語機能です。
Javaでの非同期/同時実行性のサポートは比較的少ない言語ですが、Java.util.concurrent
パッケージにはこれに関する多くの便利なクラスが含まれています。 (Task Parallel Libraryと完全に同等ではありませんが、それに最も近い近似です。)
await
は、非同期操作が完了したときに継続を使用して追加コードを実行します(client.GetStringAsync(...)
)。
したがって、最も近い近似として、CompletableFuture<T>
(.NET Task<TResult>
に相当するJava 8)ベースのソリューションを使用して、Http要求を非同期に処理します。
2016年5月25日に AsyncHttpClient v.2に更新され、2016年4月13日にリリースされました:
したがって、AccessTheWebAsync()
のOPの例に相当するJava 8は次のとおりです。
CompletableFuture<Integer> AccessTheWebAsync()
{
AsyncHttpClient asyncHttpClient = new DefaultAsyncHttpClient();
return asyncHttpClient
.prepareGet("http://msdn.Microsoft.com")
.execute()
.toCompletableFuture()
.thenApply(Response::getResponseBody)
.thenApply(String::length);
}
この使用法は、 Async HttpクライアントリクエストからCompletableFutureを取得する方法 および AsyncHttpClient のバージョン2で提供された新しいAPIに基づく回答から取得されました2016年4月13日には、CompletableFuture<T>
のサポートが既に組み込まれています。
AsyncHttpClientのバージョン1を使用した元の回答:
そのために、2つの可能なアプローチがあります。
最初のものはノンブロッキングIOを使用し、私はそれをAccessTheWebAsyncNio
と呼びます。ただし、AsyncCompletionHandler
は(機能的なインターフェイスではなく)抽象クラスなので、ラムダを引数として渡すことはできません。そのため、匿名クラスの構文により、避けられない冗長性が生じます。ただし、このソリューションは、指定されたC#の例の実行フローに最も近い。
2番目のものは少し冗長ですが、応答が完了するまでf.get()
のスレッドを最終的にブロックする新しいTaskを送信します。
最初のアプローチ、より冗長だが非ブロッキング:
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());
}
}
ea-async を確認してください。これはJavaバイトコードの書き換えを行い、非同期/待機をかなりうまくシミュレートします。彼らのreadmeによると:「.NET CLRでのAsync-Awaitに大きな影響を受けています」
asyncおよびawaitは構文糖衣です。非同期と待機の本質はステートマシンです。コンパイラは、非同期/待機コードを状態マシンに変換します。
同時に、async/awaitを実際のプロジェクトで実際に実行できるようにするには、多くの非同期I/Oライブラリ関数がすでに設定されている必要があります。 C#の場合、ほとんどの元の同期I/O関数には代替の非同期バージョンがあります。これらの非同期関数が必要な理由は、ほとんどの場合、独自の非同期/待機コードが何らかのライブラリ非同期メソッドに要約されるためです。
C#の非同期バージョンライブラリ関数は、JavaのAsynchronousChannelの概念に似ています。たとえば、AsynchronousFileChannel.readを使用すると、Futureを返すか、読み取り操作の完了後にコールバックを実行できます。しかし、まったく同じではありません。すべてのC#非同期関数は、タスクを返します(Futureに似ていますが、Futureよりも強力です)。
Javaがasync/awaitをサポートしているとしましょう。次のようなコードを記述します。
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);
}
}
言語レベルのJavaには、C#async/awaitに相当するものはありません。 Fibers akacooperative threadsakalightweight threadsとして知られる概念は、興味深い代替案です。繊維のサポートを提供するJavaライブラリを見つけることができます。
ファイバーを実装するJavaライブラリ
この記事(Quasarから) を読んで、繊維の素晴らしい紹介を読んでください。スレッドとは何か、ファイバーをJVMに実装する方法をカバーし、Quasar固有のコードが含まれています。
言及されたように、直接同等のものはありませんが、Javaバイトコードを変更すると(async/awaitのような命令と基礎となる継続の実装の両方で)非常に近い近似を作成できます。
JavaFlow continuation ライブラリの上にasync/awaitを実装するプロジェクトに取り組んでいます。チェックしてください https://github.com/vsilaev/Java-async-await
Maven mojoはまだ作成されていませんが、提供されたJavaエージェントを使用して例を実行できます。 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はメソッドに非同期実行可能フラグを設定するアノテーションです。await()は継続を使用してCompletableFutureで待機する関数であり、「return asyncResult(someValue)」への呼び出しは関連するCompletableFuture/Continuationを確定します。
C#と同様に、制御フローは保持され、例外処理は通常の方法で実行できます(順次実行されるコードのように試行/キャッチします)
Java自体には同等の機能はありませんが、同様の機能を提供するサードパーティライブラリが存在します(例: Kilim )。
Async/awaitキーワードのようにこれを行うことができるJava固有のものはありませんが、本当にしたい場合にできることは CountDownLatch を使用することです。あなたはcouldし、(少なくともJava7で)これを渡すことでasync/awaitを模倣します。これはAndroidユニットテストの一般的な方法で、非同期呼び出し(通常はハンドラーによってポストされた実行可能ファイル)を作成し、結果を待つ(カウントダウン)必要があります。
ただし、テストではなくアプリケーション内でこれを使用すると、ではなくが推奨されます。 CountDownLatchは、適切な回数と適切な場所で効果的にカウントダウンすることに依存しているため、これは非常に見苦しくなります。
まず、async/awaitとは何かを理解します。これは、シングルスレッドのGUIアプリケーションまたは効率的なサーバーが、単一のスレッドで複数の「ファイバー」、「コルーチン」、または「軽量スレッド」を実行する方法です。
通常のスレッドを使用しても問題ない場合、Javaに相当するのはExecutorService.submit
とFuture.get
です。これは、タスクが完了するまでブロックし、結果を返します。その間、他のスレッドが機能します。
ファイバーのようなものの利点が必要な場合は、コンテナー(GUIイベントループまたはサーバーHTTP要求ハンドラー)でのサポートが必要です。または、独自に作成する必要があります。たとえば、Servlet 3.0は非同期処理を提供します。 JavaFXはjavafx.concurrent.Task
を提供します。ただし、これらには言語機能の優雅さはありません。彼らは通常のコールバックを介して動作します。
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);
}
);
}
残念ながら、Javaにはasync/awaitに相当するものはありません。最も近いものはおそらくGuavaのListenableFutureとリスナーチェーンを使用することですが、ネストレベルが非常に急速に成長するため、複数の非同期呼び出しを伴うケースを記述するのは依然として非常に面倒です。
JVM上で別の言語を使用しても問題ない場合は、幸いなことにScalaにasync/awaitがあります。これは、ほぼ同じ構文とセマンティクスを持つ直接的なC#async/awaitと同等です。 https://github.com/scala/async/
この機能にはC#での高度なコンパイラサポートが必要でしたが、Scalaの非常に強力なマクロシステムのおかげで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バージョンでも動作します)。
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());
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();
}
});
}
});