Async-Await の同期コンテキスト

# async/await キーワードの擬似コード

単純な非同期メソッドを考えてみましょう:

async Task Foo()
{
    Bar();
    await Baz();
    Qux();
}

簡単に言うと、このコードは実際には次のことを意味していると言えます:

Task Foo()
{
    Bar();
    Task t = Baz();
    var context = SynchronizationContext.Current;
    t.ContinueWith(task) =>
    {
        if (context == null)
            Qux();
        else
            context.Post((obj) => Qux(), null);
    }, TaskScheduler.Current);

    return t;
}

async という意味です /await キーワードは、存在する場合、現在の同期コンテキストを使用します。つまりUI、Web、およびコンソール アプリケーションで正しく機能するライブラリ コードを記述できます。

ソース記事 .

# 同期コンテキストの無効化

同期コンテキストを無効にするには、ConfigureAwait を呼び出す必要があります メソッド:

async Task() Foo()
{
    await Task.Run(() => Console.WriteLine("Test"));
}

. . .

Foo().ConfigureAwait(false);

ConfigureAwait は、デフォルトの SynchronizationContext キャプチャ動作を回避する手段を提供します。 flowContext パラメーターに false を渡すと、待機後に SynchronizationContext を使用して実行を再開できなくなります。

SynchronizationContext がすべてです からの引用

# SynchronizationContext が重要な理由

次の例を考えてみましょう:

private void button1_Click(object sender, EventArgs e)
{
    label1.Text = RunTooLong();
}

このメソッドは、RunTooLong まで UI アプリケーションをフリーズします。 完成します。アプリケーションが応答しなくなります。

内部コードを非同期で実行してみることができます:

private void button1_Click(object sender, EventArgs e)
{
    Task.Run(() => label1.Text = RunTooLong());
}

しかし、内部本体は非 UI スレッドで実行される可能性があり、UI プロパティを直接変更するべきではないため、このコードは実行されません:

private void button1_Click(object sender, EventArgs e)
{
    Task.Run(() =>
    {
        var label1Text = RunTooLong();

        if (label1.InvokeRequired)
            lable1.BeginInvoke((Action) delegate() { label1.Text = label1Text; });
        else
            label1.Text = label1Text;
    });
}

常にこのパターンを使用することを忘れないでください。または、SynchronizationContext.Post を試してください それはあなたのためにそれを作るでしょう:

private void button1_Click(object sender, EventArgs e)
{
    Task.Run(() =>
    {
        var label1Text = RunTooLong();
        SynchronizationContext.Current.Post((obj) =>
        {
            label1.Text = label1    Text);
        }, null);
    });
}