web-dev-qa-db-ja.com

C#のasync / TaskコンストラクトはJavaのExecutor / Futureと同等ですか?

私は長い間Java=開発者ですが、SEへのトラフィックが少ないため、表示を単一のタグに制限していません。async/ awaitを使用したC#の質問が来ることに気付きました私が読んだ限りでは、それはC#の標準(ただし最近の)非同期プログラミングメカニズムであり、JavaのExecutorFuture、またはおそらくより正確に相当します。 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.
}
_
7
Kayaman

C#のTaskは、JavaのFutureCompletableFutureの中間にあります。 Resultプロパティは、get()を呼び出すのと同じです。ContinueWith()は、CompletableFutureの継続関数の大規模な配列が行うことを実行します(そこに_Task.WhenAny_と_Task.WhenAll_を追加します)。ただし、complete(T)には同等の機能がなく(TaskCompletionSourceを使用)、cancel()(明示的なCancellationTokensを渡す)もありません。

JavaのExecutorはC#ではほとんど隠されています。 _Task.Run_によって自動的に使用されるForkJoinPoolに相当するグローバルスレッドプールがあります。本当に実行を制御したいが、ほとんど使用しない場合、TaskSchedulerがあります。

ただし、async/awaitには、Javaで同等のものはありません。ここでの重要な点は、_await someTask_がnotsomeFuture.get()と同じであることです。後者は、将来が完了するまで実行中のスレッドをブロックします。

前者はまったく違うことをします。概念的には、コルーチンのようなものを実装します。タスクが完了していない場合は、現在の関数の実行を一時停止しますが、スレッドを解放して他の作業を続行します。タスクが完了すると、awaitのポイントから実行が継続されます。

実際には、これはコンパイラーの変換です。コンパイラは、async関数を断片に分割します。関数を呼び出すと、awaitに達するまで最初のピースが実行されます。タスクが完了すると、次のピースが実行されます。それ以外の場合は、ContinueWithを使用して、タスクが完了すると次のピースが実行されるようにスケジュールします。

これは重要な違いです。これは、待機しているものがネットワークアクセスでブロックされている場合、プールのスレッドを使い果たしていないことを意味するためです。代わりに、スレッドは他のタスクを処理できます。

(上記の説明は簡略化されています。コンパイラは実際には関数を分割せず、ステートマシンに変換します。また、ContinueWithを使用しませんが、GetAwaiter().UnsafeOnCompletedを使用します。)

これは、継続の効率が得られることを意味しますが、通常の関数の便利なプログラミングモデルを使用します。

11
Sebastian Redl