web-dev-qa-db-ja.com

C#5 async / awaitのコンテキストを理解する

Async/await自体は並行性/並列性とは関係がなく、継続渡しスタイル(CPS)の実装にすぎないというのは正しいですか?そして、実際のスレッド化は、SynchronizationContextが通過/復元するawaitインスタンスによって実行されますか?

それが正しければ、SynchronizationContextについて次の質問があります。
継続が同じスレッドで実行されることを保証します。

ただし、スレッドのコンテキスト情報が保持されるという保証はありますか? NameCurrentPrincipalCurrentCultureCurrentUICultureなどを意味します。フレームワーク(ASP.NET、WinForms、WCF、WPF)に依存しますか?

24
UserControl

Async/await自体は並行性/並列性とは関係がなく、CPSの実装にすぎないというのは正しいですか?

さて、async/awaitはCPSを使用した書き直しなので、コアの理解は正しいです。

「並行性」と「並列性」に関しては、並行性が可能になると言えます。すべて「飛行中」の複数のasync操作を同時に開始できます。これは、_Task.WhenAll_および_Task.WhenAny_を使用して簡単に実行できます。

また、async自体は「マルチスレッド」を意味しませんが、_Task.Run_は簡単なasync互換のマルチスレッドを有効にします。

そして、実際のスレッド化は、パス/復元を待機するSynchronizationContextインスタンスによって実行されますか?

このように考えてください。CPSの書き換えによって作成された継続は、どこかで実行する必要があります。キャプチャされた「非同期コンテキスト」を使用して、継続をスケジュールできます。

補足:キャプチャされたコンテキストは、null(---)でない限り、実際には_SynchronizationContext.Current_です。nullの場合、キャプチャされたコンテキストは_TaskScheduler.Current_です。

もう1つの重要な注意事項:コンテキストのキャプチャと復元は、実際には「awaiter」オブジェクト次第です。したがって、デフォルトでは、await a Task(またはその他の組み込みの待機可能ファイル)を使用すると、コンテキストがキャプチャされて復元されます。ただし、ConfigureAwait(false)の結果をawaitした場合、コンテキストはキャプチャされません。同様に、あなたがあなた自身のカスタムをawait待っている場合、それはコンテキストをキャプチャしません(あなたがそれをプログラムしない限り)。

ただし、スレッドのコンテキスト情報が保持されるという保証はありますか?名前、CurrentPrincipal、CurrentCulture、CurrentUICultureなどを意味します。

SynchronizationContextExecutionContextとは異なります。簡単な答えは、ExecutionContextは常に「流れる」ので、CurrentPrincipalが流れるということです(そうでない場合は、セキュリティの問題である可能性があります。そのため、APIが流れないのはExecutionContextは常にUnsafeで終わります)。

UIアプリでは、カルチャは流れませんが、デフォルトでは、とにかくすべてのスレッドで同じです。 Nameは、同じスレッドで再開しない限り(たとえば、UI SynchronizationContextを使用して)、確実に流れません。


さらに読むために、私は自分自身の async/awaitチュートリアル から始めてから 公式async/await FAQ 。次に、 ExecutionContextSynchronizationContext に関するStephenToubのブログ投稿をご覧ください。

私の SynchronizationContext article も役に立ちます。

38
Stephen Cleary

いいえ、async/awaitキーワードは並行性と関係があります。 async/awaitは基本的に、メソッドコードをタスクと継続にラップします。コンパイラーが(タスク並列ライブラリーを使用して)生成する正確な変換を確認するには、コードスニペットを逆アセンブルします。 async/awaitの使用法のこの翻訳は、以下の例と「類似」しています(ただし同一ではありません!)。

async Task<int> TaskOfTResult_MethodAsync()
{
    int hours;
    // . . .
    // Return statement specifies an integer result.
    return hours;
}

// Calls to TaskOfTResult_MethodAsync
Task<int> returnedTaskTResult = TaskOfTResult_MethodAsync();
int intResult = await returnedTaskTResult;
// or, in a single statement
int intResult = await TaskOfTResult_MethodAsync();

これはおおよそに変換されます

private int Result()
{
    int hours;
    // . . .
    // Return statement specifies an integer result.
    return hours;
}

次のようなメソッドの外で戻るのを待つ場所

int? hours = null;
Task<int> task = null;
task = Task.Factory.StartNew<int>(() => Result());
task.ContnueWith(cont => 
{
    // Some task completion checking...
    hours = task.Result;
}, CancellationToken.None, 
   TaskCreationOptions.None, 
   TaskScheduler.Current);

または、TPLコードをResultメソッドに配置することもできます

private int ResultAsync()
{
    int? hours = null;
    Task<int> task = null;
    task = Task.Factory.StartNew<int>(() => 
    {
        int hours;
        // . . .
        // Return statement specifies an integer result.
        return hours;
    }, CancellationToken.None, 
       TaskCreationOptions.None, 
       TaskScheduler.Current);
    try
    {
        return task.Result;
    }
    catch (AggregateException aggEx)
    {
        // Some handler method for the agg exception.
        aggEx.Handle(HandleException); 
    }
}

SynchronizationContextは、async/awateコードの同じスレッドで継続が実行されることを保証しません。ただし、SynchronisationContexキーワードを使用して、TPLコードを使用してコンテキストを設定できます。

4
MoonKnight