63

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
    }
}

最初の値を実際に値が必要な場所に置き換え、2 つ目を待機が実際に発生awaitしている場所に置き換えました。機能は既に実装されており、意味的にはコードで実際に起こっていることにより近くなっています。Task.ResultawaitTask.Wait()(1)(2)

メソッドがイテレータと同様にステートマシンとして書き直されることは理解していasyncますが、それがもたらす利点もわかりません。動作に別のスレッドを必要とするコード (ダウンロードなど) は別のスレッドを必要とし、別のスレッドを必要としないコード (ファイルからの読み取りなど) は、TPL を利用して 1 つのスレッドのみで動作する可能性があります。

ここには明らかに何か大きなものが欠けています。誰かがこれをもう少しよく理解するのを手伝ってもらえますか?

4

7 に答える 7

71

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

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

これは実際には完全に間違っています。これらはどちらも同じ意味です。

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

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

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

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

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

于 2010-10-29T17:26:59.210 に答える
26

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

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

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

そのため、実際の計算が同時に行われる場合と行われない場合がありますが、プログラムで線形の制御フローを記述することができます。計算が同時に行われる場合、await と async を使用すると、これらの操作を構成できます。

于 2010-10-29T17:55:35.977 に答える
25

大きな違いがあります:

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

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

于 2010-10-29T17:20:52.913 に答える
6

プログラム フローの制御をステート マシンに変換できることが、これらの新しいキーワードの興味深いところです。値ではなく、コントロールを生成すると考えてください。

Anders が新機能について語っているChannel 9 のビデオをご覧ください。

于 2010-10-29T17:27:15.323 に答える
4

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

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

このように書かれていると、私には違いがはるかに明白です。このArchiveDocuments関数は、同期的に完了するものではありませんが、後で終了します。

于 2010-10-29T17:25:39.040 に答える
1

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

「await」なしで await コードがどのように見えるかを確認するには、次の優れたリンクを参照してください。 .aspx

于 2015-01-07T03:35:21.933 に答える
0

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

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

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

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

于 2010-10-29T17:16:06.483 に答える