C#5.0のAsync-Wainal機能はTPLとどのように異なりますか?
-
27-09-2019 - |
質問
C#(およびVB)の新しいASYNC機能と.NET 4.0の間で違いはありません タスクパラレルライブラリ. 。たとえば、Eric Lippertのコードを考えてみましょう ここから:
async void ArchiveDocuments(List<Url> urls) {
Task archive = null;
for(int i = 0; i < urls.Count; ++i) {
var document = await FetchAsync(urls[i]);
if (archive != null)
await archive;
archive = ArchiveAsync(document);
}
}
そうです await
キーワードは2つの異なる目的を果たしています。最初の出来事(FetchAsync
)意味するようです、 「この値が使用されている場合 メソッドの後半 そして、そのタスクは終了しません。継続する前に完了するまで待ちます。」 2番目のインスタンス(archive
)意味するようです、 「このタスクがまだ終わっていない場合は、待ってください たった今 それが完了するまで。」 私が間違っているなら、私を修正してください。
このように簡単に書くことはできませんでしたか?
void ArchiveDocuments(List<Url> urls) {
for(int i = 0; i < urls.Count; ++i) {
var document = FetchAsync(urls[i]); // removed await
if (archive != null)
archive.Wait(); // changed to .Wait()
archive = ArchiveAsync(document.Result); // added .Result
}
}
最初のものを交換しました await
とともに Task.Result
値が実際に必要であり、2番目の値が必要です await
と Task.Wait()
, 、待機が実際に発生している場所。機能はです (1)
すでに実装されています (2)
コードで実際に起こっていることにより意味的にははるかに近い。
私はそれを理解しています async
メソッドは、イテレーターと同様の状態マシンとして書き換えられますが、どのような利点がもたらすかはわかりません。操作するために別のスレッドを必要とするコード(ダウンロードなど)には別のスレッドが必要になり、(ファイルからの読み取りなど)がないコードは、単一のスレッドのみで動作するためにTPLを使用することができます。
ここでは明らかに大きなものがありません。誰かが私がこれをもう少しよく理解するのを手伝ってくれますか?
解決
ここで誤解が生じていると思います。
待ち望まれているキーワードは、2つの異なる目的を果たしているようです。最初の発生(Fetchasync)は、「この値がメソッドの後半で使用され、そのタスクが終了しない場合、継続する前に完了するまで待ってください」を意味するようです。 2番目のインスタンス(アーカイブ)は、「このタスクがまだ終了していない場合は、完了するまで今すぐ待ってください」を意味しているようです。私が間違っているなら、私を修正してください。
これは実際には完全に正しくありません。これらは両方と同じ意味を持っています。
あなたの最初のケースで:
var document = await FetchAsync(urls[i]);
ここで起こることは、ランタイムが「Fetchasyncを呼び出し始めてから、現在の実行ポイントをこの方法を呼び出すスレッドに戻す」と書かれているということです。ここには「待機」はありません - 代わりに、実行は呼び出し同期コンテキストに戻り、物事はかき回し続けます。将来のある時点で、Fetchasyncのタスクは完了し、その時点で、このコードは呼び出しスレッドの同期コンテキストで再開され、次のステートメント(ドキュメント変数の割り当て)が発生します。
その後、2回目の待望の呼び出しまで実行が続きます - その時点で、同じことが起こります - Task<T>
(アーカイブ)は完全ではなく、実行は呼び出しコンテキストにリリースされます - それ以外の場合、アーカイブが設定されます。
2番目のケースでは、事態は非常に異なります - ここでは、明示的にブロックしています。つまり、通話同期コンテキストは、メソッド全体が完了するまでコードを実行する機会を得ることはありません。確かに、まだ非同期がありますが、非同期はこのコードのブロック内に完全に含まれています。このスレッドの外側のコードは、すべてのコードが完了するまで発生しません。
他のヒント
大きな違いがあります:
Wait()
ブロック、 await
ブロックしません。 Asyncバージョンを実行した場合 ArchiveDocuments()
GUIスレッドでは、GUIはフェッチとアーカイブの操作が実行されている間、応答性を維持します。 TPLバージョンを使用する場合 Wait()
, 、GUIはブロックされます。
ご了承ください async
スレッドを導入せずにこれを行うことができます - await
, 、コントロールは単にメッセージループに戻されます。待機中のタスクが完了すると、メソッドの残り(継続)がメッセージループに囲まれ、GUIスレッドは実行され続けます ArchiveDocuments
中断したところ。
アンダースは、彼がしたチャンネル9ライブインタビューで非常に簡潔な答えにそれを煮詰めました。強くお勧めします
新しいAsyncと待望のキーワードは、あなたを許可します 同時性を調整します アプリケーションで。彼らは実際にあなたのアプリケーションに並行性を導入しません。
TPL、より具体的にはタスクです 一方通行 実際に操作を同時に実行するために使用できます。新しいAsyncと待望のキーワードは、あなたを許可します 構成します 「同期」または「線形」ファッションでのこれらの同時操作。
したがって、実際のコンピューティングが同時に発生する場合とそうでない場合がありますが、プログラムに線形制御の流れを書き込むことができます。計算が同時に発生した場合、待ち望んでいて、 構成します これらの操作。
プログラムの制御フローを状態マシンに変える機能は、これらの新しいキーワードが興味をそそられるものです。それを考えてください コントロールを生成します, 、値ではなく。
チェックアウト このチャンネル9ビデオ 新しい機能について話しているアンダースの。
ここでの問題は、の署名です ArchiveDocuments
誤解を招きます。それは明示的な返品を持っています void
しかし、実際にはリターンです Task
. 。私にとってvoidは、それが終了するのを「待つ」方法がないので、同期を意味します。関数の代替署名を考慮してください。
async Task ArchiveDocuments(List<Url> urls) {
...
}
私にとって、それがこのように書かれているとき、違いははるかに明白です。 ArchiveDocuments
機能は、同期して完了するものではなく、後で終了するものです。
通話 FetchAsync()
完了するまでブロックされます(呼び出し内のステートメントがない限り await
鍵は、コントロールが発信者に返されることです(なぜなら ArchiveDocuments
メソッド自体が宣言されています async
)。そのため、発信者はUIロジックの処理を喜んで継続し、イベントに応答します。
いつ FetchAsync()
完了すると、呼び出し元を中断してループを完了します。ヒットします ArchiveAsync()
そしてブロック、しかし ArchiveAsync()
おそらく、新しいタスクを作成し、開始し、タスクを返します。これにより、タスクが処理されている間に、2番目のループが開始できます。
2番目のループがヒットします FetchAsync()
ブロック、コントロールを発信者に戻します。いつ FetchAsync()
完了すると、処理を継続するために呼び出し元を中断します。その後、ヒットします await archive
, 、これはコントロールを発信者に戻します Task
ループ1で作成された完了。そのタスクが完了すると、発信者が再び中断され、2番目のループが呼び出されます ArchiveAsync()
, 、開始タスクを取得し、ループ3を開始し、繰り返します 広告吐き気.
重要なのは、重いリフターが実行されている間に発信者にコントロールを返すことです。
待ち気のキーワードでは、同時性が導入されません。これは、収量キーワードのようなもので、コンパイラに、状態マシンによって制御されたラムダにコードを再構築するように指示します。
待っているコードが「待っている」ことなくどのように見えるかを確認するには、この優れたリンクを参照してください。 http://blogs.msdn.com/b/windowsappdev/archive/2012/04/24/diving-deep-with-winrt-and-await.aspx