C#(およびVB)の新しい非同期機能と.NET 4.0の Task Parallel Library の違いはわかりません。たとえば、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を使用して単一のスレッドのみを処理できます。
ここには明らかに何か巨大なものがありません。誰かがこれを少しよく理解するのを助けることができますか?
ここで誤解が生じていると思います:
Awaitキーワードは2つの異なる目的を果たしているようです。最初の発生(FetchAsync)は、「この値がメソッドで後で使用され、そのタスクが完了していない場合は、完了するまで待ってから続行する」ことを意味しているようです。 2番目のインスタンス(アーカイブ)は、「このタスクがまだ完了していない場合は、完了するまで今すぐ待つ」ことを意味しているようです。私が間違っている場合は、私を訂正してください。
これは実際には完全に正しくありません。これらは両方とも同じ意味です。
あなたの最初のケースでは:
var document = await FetchAsync(urls[i]);
ここで何が起こるかというと、ランタイムは「FetchAsyncの呼び出しを開始してから、現在の実行ポイントをこのメソッドを呼び出すスレッドに返す」と言っています。ここには「待機」はありません。代わりに、実行は呼び出し側の同期コンテキストに戻り、状況は変化し続けます。将来のある時点で、FetchAsyncのタスクが完了します。その時点で、このコードは呼び出しスレッドの同期コンテキストで再開し、次のステートメント(ドキュメント変数の割り当て)が発生します。
次に、実行は2番目の待機呼び出しまで継続します。そのとき、同じことが起こります-Task<T>
(アーカイブ)は完了していません。実行は呼び出しコンテキストに解放されます。それ以外の場合は、アーカイブが設定されます。
2番目のケースでは、状況が大きく異なります。ここでは明示的にブロックしています。つまり、メソッド全体が完了するまで、呼び出し側の同期コンテキストがコードを実行する機会はありません。確かに、非同期はまだありますが、非同期はこのコードブロック内に完全に含まれています。この貼り付けられたコード以外のコードは、すべてのコードが完了するまでこのスレッドで発生しません。
大きな違いがあります:
Wait()
はブロックし、await
はブロックしません。 GUIスレッドでArchiveDocuments()
の非同期バージョンを実行すると、フェッチおよびアーカイブ操作の実行中、GUIは応答性を維持します。 Wait()
でTPLバージョンを使用すると、GUIがブロックされます。
async
は、スレッドを導入せずにこれを行うことに注意してください。await
の時点で、制御は単純にメッセージループに戻ります。待機中のタスクが完了すると、メソッドの残りの部分(継続)がメッセージループにエンキューされ、GUIスレッドは中断したところからArchiveDocuments
を実行し続けます。
Andersは、Channel 9のLiveインタビューで、非常に簡潔な答えに要約しました。私はそれを強くお勧めします
新しいAsyncキーワードとawaitキーワードを使用すると、アプリケーションで並行処理を調整できます。実際には、アプリケーションに同時実行性が導入されることはありません。
TPL、より具体的にはTaskはone wayで、実際に操作を同時に実行できます。新しいasyncおよびawaitキーワードを使用すると、「同期」または「線形」の方法でこれらの同時操作をcomposeできます。
したがって、実際の計算が同時に行われる場合と行われない場合がある場合でも、プログラムに線形制御フローを書き込むことができます。計算が同時に行われる場合、awaitおよびasyncを使用すると、これらの操作をcomposeできます。
プログラムの制御フローをステートマシンに変換する機能は、これらの新しいキーワードを魅力的なものにします。値ではなく、yielding controlと考えてください。
新機能について語るAndersの このChannel 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
_をヒットし、ループ1で作成されたTask
が完了するまで、呼び出し元に制御を返します。そのタスクが完了すると、呼び出し元は再び中断され、2番目のループがArchiveAsync()
を呼び出します。これにより、開始されたタスクが取得され、ループ3が開始されますad nauseumを繰り返します。
重要なのは、ヘビーリフターの実行中に制御を呼び出し元に戻すことです。
Awaitキーワードは同時実行性を導入しません。これはyieldキーワードのようなもので、ステートマシンによって制御されるラムダにコードを再構成するようコンパイラーに指示します。
「await」なしでawaitコードがどのように見えるかを確認するには、次の優れたリンクを参照してください。 http://blogs.msdn.com/b/windowsappdev/archive/2012/04/24/diving-deep-with-winrt -and-await.aspx