web-dev-qa-db-ja.com

C#5.0の非同期待機機能とTPLの違いは何ですか?

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を使用して単一のスレッドのみを処理できます。

ここには明らかに何か巨大なものがありません。誰かがこれを少しよく理解するのを助けることができますか?

61
zildjohn01

ここで誤解が生じていると思います:

Awaitキーワードは2つの異なる目的を果たしているようです。最初の発生(FetchAsync)は、「この値がメソッドで後で使用され、そのタスクが完了していない場合は、完了するまで待ってから続行する」ことを意味しているようです。 2番目のインスタンス(アーカイブ)は、「このタスクがまだ完了していない場合は、完了するまで今すぐ待つ」ことを意味しているようです。私が間違っている場合は、私を訂正してください。

これは実際には完全に正しくありません。これらは両方とも同じ意味です。

あなたの最初のケースでは:

var document = await FetchAsync(urls[i]);

ここで何が起こるかというと、ランタイムは「FetchAsyncの呼び出しを開始してから、現在の実行ポイントをこのメソッドを呼び出すスレッドに返す」と言っています。ここには「待機」はありません。代わりに、実行は呼び出し側の同期コンテキストに戻り、状況は変化し続けます。将来のある時点で、FetchAsyncのタスクが完了します。その時点で、このコードは呼び出しスレッドの同期コンテキストで再開し、次のステートメント(ドキュメント変数の割り当て)が発生します。

次に、実行は2番目の待機呼び出しまで継続します。そのとき、同じことが起こります-Task<T>(アーカイブ)は完了していません。実行は呼び出しコンテキストに解放されます。それ以外の場合は、アーカイブが設定されます。

2番目のケースでは、状況が大きく異なります。ここでは明示的にブロックしています。つまり、メソッド全体が完了するまで、呼び出し側の同期コンテキストがコードを実行する機会はありません。確かに、非同期はまだありますが、非同期はこのコードブロック内に完全に含まれています。この貼り付けられたコード以外のコードは、すべてのコードが完了するまでこのスレッドで発生しません。

71
Reed Copsey

大きな違いがあります:

Wait()はブロックし、awaitはブロックしません。 GUIスレッドでArchiveDocuments()の非同期バージョンを実行すると、フェッチおよびアーカイブ操作の実行中、GUIは応答性を維持します。 Wait()でTPLバージョンを使用すると、GUIがブロックされます。

asyncは、スレッドを導入せずにこれを行うことに注意してください。awaitの時点で、制御は単純にメッセージループに戻ります。待機中のタスクが完了すると、メソッドの残りの部分(継続)がメッセージループにエンキューされ、GUIスレッドは中断したところからArchiveDocumentsを実行し続けます。

25
Daniel

Andersは、Channel 9のLiveインタビューで、非常に簡潔な答えに要約しました。私はそれを強くお勧めします

新しいAsyncキーワードとawaitキーワードを使用すると、アプリケーションで並行処理を調整できます。実際には、アプリケーションに同時実行性が導入されることはありません。

TPL、より具体的にはTaskはone wayで、実際に操作を同時に実行できます。新しいasyncおよびawaitキーワードを使用すると、「同期」または「線形」の方法でこれらの同時操作をcomposeできます。

したがって、実際の計算が同時に行われる場合と行われない場合がある場合でも、プログラムに線形制御フローを書き込むことができます。計算が同時に行われる場合、awaitおよびasyncを使用すると、これらの操作をcomposeできます。

24
Brad Cunningham

プログラムの制御フローをステートマシンに変換する機能は、これらの新しいキーワードを魅力的なものにします。値ではなく、yielding controlと考えてください。

新機能について語るAndersの このChannel 9ビデオ をチェックしてください。

6
John Leidegren

ここでの問題は、ArchiveDocumentsの署名が誤解を招くということです。 voidの明示的な戻り値がありますが、実際の戻り値はTaskです。終了するのを「待つ」方法がないので、私にとってvoidは同期を意味します。関数の代替署名を検討してください。

async Task ArchiveDocuments(List<Url> urls) { 
  ...
}

このように書くと、違いははるかに明白になります。 ArchiveDocuments関数は、同期的に完了するものではなく、後で完了する関数です。

4
JaredPar

FetchAsync()の呼び出しは、完了するまでブロックされます(呼び出し内のステートメントがawait?でない限り)。重要なのは、制御が呼び出し元に返されることです(ArchiveDocumentsメソッドが原因で)自体はasync)として宣言されます。したがって、呼び出し元はUIロジックの処理を続行したり、イベントに応答したりできます。

FetchAsync()が完了すると、呼び出し元に割り込んでループを終了します。 ArchiveAsync()にヒットしてブロックしますが、ArchiveAsync()はおそらく新しいタスクを作成して開始し、タスクを返します。これにより、タスクの処理中に2番目のループを開始できます。

2番目のループはFetchAsync()にヒットしてブロックし、呼び出し元に制御を返します。 FetchAsync()が完了すると、呼び出し元に再び割り込んで処理を続行します。次に、_await archive_をヒットし、ループ1で作成されたTaskが完了するまで、呼び出し元に制御を返します。そのタスクが完了すると、呼び出し元は再び中断され、2番目のループがArchiveAsync()を呼び出します。これにより、開始されたタスクが取得され、ループ3が開始されますad nauseumを繰り返します。

重要なのは、ヘビーリフターの実行中に制御を呼び出し元に戻すことです。

0
James King

Awaitキーワードは同時実行性を導入しません。これはyieldキーワードのようなもので、ステートマシンによって制御されるラムダにコードを再構成するようコンパイラーに指示します。

「await」なしでawaitコードがどのように見えるかを確認するには、次の優れたリンクを参照してください。 http://blogs.msdn.com/b/windowsappdev/archive/2012/04/24/diving-deep-with-winrt -and-await.aspx

0
Tb.