私は長い間Java=開発者ですが、SEへのトラフィックが少ないため、表示を単一のタグに制限していません。async/ awaitを使用したC#の質問が来ることに気付きました私が読んだ限りでは、それはC#の標準(ただし最近の)非同期プログラミングメカニズムであり、JavaのExecutor
、Future
、またはおそらくより正確に相当します。 CompatibleFuture 構成(または非常に古いコードの場合はwait()/notify()
)。
Async/awaitは便利なツールのようで、すばやく記述でき、理解しやすいように見えますが、質問によって、人々がすべて非同期で、これは悪いことでもありません。
Javaでは、作業を別のスレッドにできるだけ頻繁にオフロードしようとするコードは非常に奇妙に見え、実際のパフォーマンスの利点は、作業が別のスレッドに分割されている特定のよく考えられた場所から得られます。
スレッドモデルが異なるため、矛盾はありますか? C#はよくよく知られているプラットフォームを備えたデスクトップコードですが、Javaは最近主にサーバー側にありますか?または、その関連性の歪んだイメージを取得したばかりですか?
https://markheath.net/post/async-antipatterns から:
_foreach(var c in customers)
{
await SendEmailAsync(c);
}
_
受け入れ可能なコードの例として、これは「すべてを非同期にする、いつでもawait
を使用して同期できる」と示唆しているようです。それは、await
がすでにスレッドを切り替える理由がないことをすでに示しているためですが、Javaでは
_for(Customer c : customer) {
Future<Result> res = sendEmailAsync(c);
res.get();
}
_
実際には別のスレッドで作業を実行しますが、get()
はブロックするため、順次作業になりますが、2つのスレッドを使用しますか?
もちろん、Javaの場合、標準のイディオムは、実装に事前に決定させるのではなく、呼び出し側に非同期にする必要があるかどうかを決定させることです。
_for(Customer c : customer) {
CompletableFuture<Void> cf = CompletableFuture.runAsync(() -> sendEmailSync(c));
// cf can be further used for chaining, exception handling, etc.
}
_
C#のTask
は、JavaのFuture
とCompletableFuture
の中間にあります。 Result
プロパティは、get()
を呼び出すのと同じです。ContinueWith()
は、CompletableFuture
の継続関数の大規模な配列が行うことを実行します(そこに_Task.WhenAny
_と_Task.WhenAll
_を追加します)。ただし、complete(T)
には同等の機能がなく(TaskCompletionSource
を使用)、cancel()
(明示的なCancellationToken
sを渡す)もありません。
JavaのExecutor
はC#ではほとんど隠されています。 _Task.Run
_によって自動的に使用されるForkJoinPool
に相当するグローバルスレッドプールがあります。本当に実行を制御したいが、ほとんど使用しない場合、TaskScheduler
があります。
ただし、async
/await
には、Javaで同等のものはありません。ここでの重要な点は、_await someTask
_がnotがsomeFuture.get()
と同じであることです。後者は、将来が完了するまで実行中のスレッドをブロックします。
前者はまったく違うことをします。概念的には、コルーチンのようなものを実装します。タスクが完了していない場合は、現在の関数の実行を一時停止しますが、スレッドを解放して他の作業を続行します。タスクが完了すると、await
のポイントから実行が継続されます。
実際には、これはコンパイラーの変換です。コンパイラは、async
関数を断片に分割します。関数を呼び出すと、await
に達するまで最初のピースが実行されます。タスクが完了すると、次のピースが実行されます。それ以外の場合は、ContinueWith
を使用して、タスクが完了すると次のピースが実行されるようにスケジュールします。
これは重要な違いです。これは、待機しているものがネットワークアクセスでブロックされている場合、プールのスレッドを使い果たしていないことを意味するためです。代わりに、スレッドは他のタスクを処理できます。
(上記の説明は簡略化されています。コンパイラは実際には関数を分割せず、ステートマシンに変換します。また、ContinueWith
を使用しませんが、GetAwaiter().UnsafeOnCompleted
を使用します。)
これは、継続の効率が得られることを意味しますが、通常の関数の便利なプログラミングモデルを使用します。