web-dev-qa-db-ja.com

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

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のスレッドで実行されないため、例外が発生します。

これまでのところ、これを確認するためのリソースは見つかりませんでした。これについて誰か知っていますか?これがどのように機能するかを技術的に説明する文書はありますか?

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

16
Alex

ここでいくつか混乱していると思います。あなたが求めているのは、C#ではSystem.Threading.Tasksasyncawaitを使用してすでに可能です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スレッド)で実行する必要があることを伝えます。

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

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;
}

これらの2つは非常によく似ているはずです。実際、は非常によく似ていますDelayedAddAsyncメソッドはintではなくTask<int>を返すようになりました。そのため、awaitは継続をそれぞれに継続しているだけです。主な違いは、各行の同期コンテキストに沿って渡されるため、前の例で行ったように明示的に行う必要がないことです。

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

これがawaitの本当に興味深いところです。つまり、待機者が同じメソッドをブロック呼び出しなしでジャンプしたり、ジャンプしたりできるという事実です。 awaitを呼び出すと、現在のスレッドがメッセージの処理に戻り、処理が完了すると、ウェイターは中断したところと同じスレッドで中断したところを正確にピックアップします。ただし、Invoke/BeginInvoke対照的に、あなたはずっと前にそれをやめるべきだったと申し訳ありません。

17
Aaronaught

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

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

編集:そしてここにAndersの// build /プレゼンテーションがあります: channel9

ところで、「// build /」を上下逆にすると「/ plinq //」になることに気づきましたか;-)

4
Nicholas Butler

Jon SkeetがC#5の非同期メソッドをカバーする優れたプレゼンテーションを行いました。

http://skillsmatter.com/podcast/home/async-methods

3
Sean Holm