私はC#5の新しい非同期のものを検討してきましたが、1つの特定の質問が浮上しました。
await
キーワードは、実装するための巧妙なコンパイラトリック/シンタックスシュガーであることを理解しています 継続渡し 、メソッドの残りの部分はTask
オブジェクトに分割され、キューに入れられます-順番に実行されますが、制御は呼び出し元のメソッドに返されます。
私の問題は、現在これがすべて単一のスレッド上にあると聞いたことです。これは、この非同期のものが、継続コードをTask
オブジェクトに変換し、各タスクが完了した後、次のタスクを開始する前にApplication.DoEvents()
を呼び出す方法にすぎないことを意味しますか?
それとも私は何かが足りないのですか? (質問のこの部分は修辞的です-私は完全に私が行方不明であることを知っていますsomething:))
多くの未解決の非同期操作がいつでも進行している可能性があるという意味で、これは並行です。 マルチスレッドである場合とそうでない場合があります。
デフォルトでは、await
は継続を「現在の実行コンテキスト」にスケジュールします。 「現在の実行コンテキスト」は、null
でない場合はSynchronizationContext.Current
、SynchronizationContext
がない場合は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
が使用された場合のように再入可能性の問題を引き起こさないため、はるかにクリーンなソリューションです。
Async/awaitの背後にある全体的なアイデアは、継続渡しを適切に実行し、操作に新しいスレッドを割り当てないことです。継続mayは新しいスレッドで発生し、may同じスレッドで継続します。
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
がなければなりません)
私が説明したいのは、「await」キーワードは単にタスクが終了するのを待つが、待機している間は呼び出し元のスレッドに実行を与えるということです。次に、タスクの結果を返し、タスクが完了すると、「await」キーワードの後のステートメントから続行します。
私が気付いた人の中には、タスクが呼び出し元のスレッドと同じスレッドで実行されていると思っている人もいます。これは正しくなく、呼び出しを待つメソッド内のWindows.FormsGUI要素を変更しようとすることで証明できます。ただし、継続は可能な限り呼び出し元のスレッドで実行されます。
これは、タスクの完了時にコールバックデリゲートやイベントハンドラーを用意する必要がないための優れた方法です。
この質問には、人々にとってもっと簡単な答えが必要だと思います。だから私は過度に単純化するつもりです。
事実は、タスクを保存し、それらを待たない場合、非同期/待機は「並行」です。
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);
同時です。
私はまた、人々にこれをより深く理解して読んでもらうことを勧めますが、非同期/待機を使用して並行性を得ることができ、それは非常に簡単です。