web-dev-qa-db-ja.com

C#5 Async / Await-それは*同時*ですか?

私はC#5の新しい非同期のものを検討してきましたが、1つの特定の質問が浮上しました。

awaitキーワードは、実装するための巧妙なコンパイラトリック/シンタックスシュガーであることを理解しています 継続渡し 、メソッドの残りの部分はTaskオブジェクトに分割され、キューに入れられます-順番に実行されますが、制御は呼び出し元のメソッドに返されます。

私の問題は、現在これがすべて単一のスレッド上にあると聞いたことです。これは、この非同期のものが、継続コードをTaskオブジェクトに変換し、各タスクが完了した後、次のタスクを開始する前にApplication.DoEvents()を呼び出す方法にすぎないことを意味しますか?

それとも私は何かが足りないのですか? (質問のこの部分は修辞的です-私は完全に私が行方不明であることを知っていますsomething:))

58
Neil Barnwell

多くの未解決の非同期操作がいつでも進行している可能性があるという意味で、これは並行です。 マルチスレッドである場合とそうでない場合があります。

デフォルトでは、awaitは継続を「現在の実行コンテキスト」にスケジュールします。 「現在の実行コンテキスト」は、nullでない場合はSynchronizationContext.CurrentSynchronizationContextがない場合はTaskScheduler.Currentとして定義されます。

このデフォルトの動作をオーバーライドするには、ConfigureAwaitを呼び出し、falseパラメーターにcontinueOnCapturedContextを渡します。その場合、継続はその実行コンテキストにスケジュールされません。これは通常、スレッドプールスレッドで実行されることを意味します。

ライブラリコードを記述しているのでない限り、デフォルトの動作はまさに望ましいものです。 WinForms、WPF、およびSilverlight(つまり、すべてのUIフレームワーク)はSynchronizationContextを提供するため、継続はUIスレッドで実行されます(UIオブジェクトに安全にアクセスできます)。 ASP.NETは、継続が正しい要求コンテキストで実行されることを保証するSynchronizationContextも提供します。

他のスレッド(スレッドプールスレッド、Thread、およびBackgroundWorkerを含む)は、SynchronizationContextを提供しません。したがって、コンソールアプリとWin32サービスには、デフォルトでSynchronizationContextがまったくありません。この状況では、継続はスレッドプールスレッドで実行されます。これが、await/asyncを使用したコンソールアプリのデモにConsole.ReadLine/ReadKeyへの呼び出しを含めるか、WaitでブロックTaskを実行する理由です。

SynchronizationContextが必要な場合は、私の Nito.AsyncEx ライブラリから AsyncContext を使用できます。基本的には、async互換の「メインループ」とSynchronizationContextを提供するだけです。コンソールアプリに役立ちます およびユニットテスト (VS2012には、async Task単体テストのサポートが組み込まれています)。

SynchronizationContextの詳細については、 2月のMSDN記事 を参照してください。

DoEventsまたは同等のものが呼び出されることはありません。むしろ、制御フローは完全に戻り、継続(関数の残りの部分)は後で実行されるようにスケジュールされています。これは、DoEventsが使用された場合のように再入可能性の問題を引き起こさないため、はるかにクリーンなソリューションです。

52
Stephen Cleary

Async/awaitの背後にある全体的なアイデアは、継続渡しを適切に実行し、操作に新しいスレッドを割り当てないことです。継続mayは新しいスレッドで発生し、may同じスレッドで継続します。

5
Polynomial

Async/awaitの実際の「肉」(非同期)部分は通常個別に行われ、呼び出し元への通信はTaskCompletionSourceを介して行われます。ここに書かれているように http://blogs.msdn.com/b/pfxteam/archive/2009/06/02/9685804.aspx

TaskCompletionSourceタイプは、2つの関連する目的を果たします。どちらもその名前で暗示されています。それは、タスクを作成するためのソースと、そのタスクの完了のためのソースです。本質的に、TaskCompletionSourceは、タスクとその完了のプロデューサーとして機能します。

例は非常に明確です:

public static Task<T> RunAsync<T>(Func<T> function) 
{ 
    if (function == null) throw new ArgumentNullException(“function”); 
    var tcs = new TaskCompletionSource<T>(); 
    ThreadPool.QueueUserWorkItem(_ => 
    { 
        try 
        {  
            T result = function(); 
            tcs.SetResult(result);  
        } 
        catch(Exception exc) { tcs.SetException(exc); } 
    }); 
    return tcs.Task; 
}

TaskCompletionSourceを介して、待機できるTaskオブジェクトにアクセスできますが、マルチスレッドを作成したのはasync/awaitキーワードを介してではありません。

多くの「遅い」関数が非同期/待機構文に変換される場合、TaskCompletionSourceをあまり使用する必要がないことに注意してください。彼らはそれを内部的に使用します(しかし、非同期の結果を得るには、最終的にどこかにTaskCompletionSourceがなければなりません)

2
xanatos

私が説明したいのは、「await」キーワードは単にタスクが終了するのを待つが、待機している間は呼び出し元のスレッドに実行を与えるということです。次に、タスクの結果を返し、タスクが完了すると、「await」キーワードの後のステートメントから続行します。

私が気付いた人の中には、タスクが呼び出し元のスレッドと同じスレッドで実行されていると思っている人もいます。これは正しくなく、呼び出しを待つメソッド内のWindows.FormsGUI要素を変更しようとすることで証明できます。ただし、継続は可能な限り呼び出し元のスレッドで実行されます。

これは、タスクの完了時にコールバックデリゲートやイベントハンドラーを用意する必要がないための優れた方法です。

1

この質問には、人々にとってもっと簡単な答えが必要だと思います。だから私は過度に単純化するつもりです。

事実は、タスクを保存し、それらを待たない場合、非同期/待機は「並行」です。

var a = await LongTask1(x);
var b = await LongTask2(y);

var c = ShortTask(a, b);

同時ではありません。 LongTask1は、LongTask2が開始する前に完了します。

var a = LongTask1(x);
var b = LongTask2(y);

var c = ShortTask(await a, await b);

同時です。

私はまた、人々にこれをより深く理解して読んでもらうことを勧めますが、非同期/待機を使用して並行性を得ることができ、それは非常に簡単です。

0
PRMan