C#5非同期サポートはUIスレッドの同期の問題をどのように支援しますか?


16

どこかで、C#5 async-awaitは非常に素晴らしいので、これを行うことを心配する必要はないと聞いた:

if (InvokeRequired)
{
    BeginInvoke(...);
    return;
}
// do your stuff here

待機操作のコールバックは、呼び出し元の元のスレッドで発生するようです。Eric LippertとAnders Hejlsbergは、この機能がUI(特にタッチデバイスUI)の応答性を高める必要性に起因すると何度か述べています。

このような機能の一般的な使用法は次のようになると思います。

public class Form1 : Form
{
    // ...
    async void GetFurtherInfo()
    {
        var temperature = await GetCurrentTemperatureAsync();
        label1.Text = temperature;
    }
}

コールバックのみが使用される場合、ラベルテキストを設定すると、UIのスレッドで実行されないため、例外が発生します。

これまでのところ、これが事実であることを確認するリソースが見つかりませんでした。誰もこれについて知っていますか?これがどのように機能するかを技術的に説明するドキュメントはありますか?

「はい」と答えるだけでなく、信頼できるソースからのリンクを提供してください。


少なくともawait機能性に関する限り、これは非常にありそうにないようです。これは、継続の受け渡しのための単なる構文上の砂糖です。おそらく、WinFormsに関係のない他の改善点がいくつかありますが、それらは役立つはずです ただし、これは.NETフレームワーク自体に該当し、C#には該当しません。
アーロンノート

@Aaronaught私は同意します、これが私が質問を正確に尋ねている理由です。質問を編集して、どこから来たのかを明確にしました。彼らがこの機能を作成し、それでも悪名高いInvokeRequiredスタイルのコードを使用する必要があるのは奇妙に聞こえます。
アレックス

回答:


17

ここで混乱していることがいくつかあると思います。何を求めていることである、すでに使用可能System.Threading.TasksasyncおよびawaitC#5には、ちょうど同じ機能のために少しよりよい構文糖を提供しようとしています。

Winformsの例を使用してみましょう-フォームにボタンとテキストボックスをドロップし、次のコードを使用します。

private void button1_Click(object sender, EventArgs e)
{
    Task.Factory.StartNew<int>(() => DelayedAdd(5, 10))
        .ContinueWith(t => DelayedAdd(t.Result, 20))
        .ContinueWith(t => DelayedAdd(t.Result, 30))
        .ContinueWith(t => DelayedAdd(t.Result, 50))
        .ContinueWith(t => textBox1.Text = t.Result.ToString(),
            TaskScheduler.FromCurrentSynchronizationContext());
}

private int DelayedAdd(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

実行すると、(a)UIスレッドがブロックされず、(b)通常の「スレッド間操作が無効です」というエラーが表示されません- TaskScheduler最後の引数を削除しない限りContinueWith、どの場合になります。

これは、湿地標準の継続渡しスタイルです。魔法はTaskSchedulerクラス、特にによって取得されたインスタンスで発生しますFromCurrentSynchronizationContext。これを継続に渡すと、FromCurrentSynchronizationContextメソッドを呼び出したスレッド(この場合はUIスレッド)で継続を実行する必要があることを伝えます。

ウェイターは、開始したスレッドと継続を実行する必要のあるスレッドを認識しているという意味で、わずかに洗練されています。したがって、上記のコードはもう少し自然に書くことができます。

private async void button1_Click(object sender, EventArgs e)
{
    int a = await DelayedAddAsync(5, 10);
    int b = await DelayedAddAsync(a, 20);
    int c = await DelayedAddAsync(b, 30);
    int d = await DelayedAddAsync(c, 50);
    textBox1.Text = d.ToString();
}

private async Task<int> DelayedAddAsync(int a, int b)
{
    Thread.Sleep(500);
    return a + b;
}

これら二つは非常に似ているはずですし、実際に彼らはある非常に似ています。DelayedAddAsync方法について返しTask<int>の代わりにint、そのためawaitだけのもののそれぞれに継続を叩きされます。主な違いは、各行で同期コンテキストを渡すことです。したがって、前の例で行ったように明示的に行う必要はありません。

理論的には、違いははるかに重要です。2番目の例では、button1_Clickメソッドのすべての行が実際にUIスレッドで実行されますが、タスク自体(DelayedAddAsync)はバックグラウンドで実行されます。最初の例では、UIスレッドの同期コンテキストに明示的にアタッチした割り当てを除きすべてバックグラウンドで実行されます。textBox1.Text

それは本当に興味深いことですawait-待機者がブロッキング呼び出しなしで同じメソッドに出入りできるという事実。あなたは、呼び出しawait、現在のスレッドがバックメッセージの処理に移行し、それが終了したとき、それがでオフに左に同じスレッドで、中断したところ、awaiterは正確にピックアップします。しかし、あなたの面でInvoke/のBeginInvoke「私は、問題のコントラストずいぶん前にそれをやめるべきだったと言って申し訳ありません。


それは非常に興味深い@Aaronaughtです。継続の受け渡しスタイルは知っていましたが、この「同期コンテキスト」のこと全体は知りませんでした。この同期コンテキストをC#5 async-awaitにリンクするドキュメントはありますか?私はそれが既存の機能であることを理解していますが、彼らがデフォルトでそれを使用しているという事実は大したことのように聞こえます。それについてのコメントはありますか?ところであなたの答えをありがとう。
アレックス

1
@Alex:これらのすべてのフォローアップの質問に対する回答については、非同期パフォーマンス:非同期および待機のコストを理解することをお勧めします。「コンテキストに関する注意」セクションでは、それが同期コンテキストにどのように関連するかを説明しています。
アーロンノート

(ちなみに、同期コンテキストは新しいものではありません。2.0からフレームワーク内にあります
。TPL

2
InvokeRequiredスタイルの使用についてまだ多くの議論があり、私が見たスレッドのほとんどが同期コンテキストについても言及していないのはなぜだろうと思っています。この質問を立てる時間を節約できただろう
アレックス

2
@アレックス:あなたは正しい場所を見ていなかったと思います。何を言ったらいいかわかりません。.NETコミュニティの大部分は、追いつくのに長い時間がかかります。地獄、私はまだArrayList新しいコードでクラスを使用しているコーダーを見ています。私自身、RXの経験はまだほとんどありません。人々は、すでに知っていることは古くなっていても、知っておくべきことを学び、すでに知っていることを共有します。この答えは数年後には時代遅れになるかもしれません。
アーロンノート

4

はい、UIスレッドの場合、待機操作のコールバックは呼び出し元の元のスレッドで発生します。

Eric Lippertは1年前に8部構成のシリーズを書きました:Fabulous Adventures In Coding

編集:Anders '// build / presentation:channel9

ところで、「// build /」を逆さまにすると、「/ plinq //」になります;-)


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.