0

私はまだasync/を学んawaitでいるので、何か明白なことを求めているのなら失礼します。次の例を考えてみましょう。

class Program  {

    static void Main(string[] args) {
        var result = FooAsync().Result;
        Console.WriteLine(result);
    }

    static async Task<int> FooAsync() {

        var t1 = Method1Async();
        var t2 = Method2Async();

        var result1 = await t1;
        var result2 = await t2;

        return result1 + result2;

    }

    static Task<int> Method1Async() {
        return Task.Run(
            () => {
                Thread.Sleep(1000);
                return 11;
            }
        );
    }

    static Task<int> Method2Async() {
        return Task.Run(
            () => {
                Thread.Sleep(1000);
                return 22;
            }
        );
    }

}

これは期待どおりに動作し、コンソールに「33」を出力します。

2番目 awaitを明示的な待機に置き換えると...

static async Task<int> FooAsync() {

    var t1 = Method1Async();
    var t2 = Method2Async();

    var result1 = await t1;
    var result2 = t2.Result;

    return result1 + result2;

}

...私は同じ振る舞いをするようです。

これらの2つの例は完全に同等ですか?

そして、この場合、それらが同等である場合、最後awaitを明示的な待機に置き換えると違いが生じる他のケースはありますか?

4

3 に答える 3

2

置換バージョンは、タスクの終了を待機している呼び出し元のスレッドをブロックします。Mainで意図的にブロックしているため、このようなコンソールアプリで目に見える違いを確認するのは難しいですが、それらは完全に同等ではありません。

于 2012-09-29T14:44:08.047 に答える
2

それらは同等ではありません。

Task.Result結果が利用可能になるまでブロックします。私のブログで説明しているように、排他的アクセスを必要とするコンテキスト(UIやASP.NETアプリなど)がある場合、これによりデッドロックが発生する可能性があります。async

また、Task.Result例外をでラップするAggregateExceptionため、同期的にブロックするとエラー処理が難しくなります。

于 2012-09-29T14:46:30.483 に答える
0

OK、私はこれを理解したと思うので、要約させてください。これまでに提供された回答よりも完全な説明になることを願っています...

短い答え

2番目を明示的な待機に置き換えてもawait、コンソールアプリケーションに大きな影響はありませんが、待機中はWPFまたはWinFormsアプリケーションのUIスレッドがブロックされます。

また、例外処理はわずかに異なります(Stephen Clearyによると)。

長い答え

一言で言えば、awaitこれを行います:

  1. 待機中のタスクがすでに終了している場合は、結果を取得して続行します。
  2. そうでない場合は、継続(メソッドの残りの部分await)を現在の同期コンテキストに送信します(存在する場合)。本質的に、await私たちが始めたところに私たちを戻そうとしています。
    • 現在のコンテキストがない場合は、元のTaskScheduler(通常はスレッドプール)を使用します。

2番目(および3番目など...)awaitも同じことを行います。

通常、コンソールアプリケーションには同期コンテキストがないため、継続は通常スレッドプールによって処理されます。したがって、継続内でブロックしても問題はありません。

一方、WinFormsまたはWPFには、メッセージループの上に同期コンテキストが実装されています。したがって、awaitUIスレッドで実行すると、(最終的には)UIスレッドでも継続が実行されます。継続でブロックすると、メッセージループがブロックされ、ブロックを解除するまでUIが応答しなくなります。OTOH、私たちがちょうどそうすればawait、UIスレッドをブロックすることなく、最終的にUIスレッドで実行される継続をきちんと投稿します。

1つのボタンと1つのラベルを含む次のWinFormsフォームで、を使用するとawait、UIが常に応答し続けます(asyncクリックハンドラーの前にあることに注意してください)。

public partial class Form1 : Form {

    public Form1() {
        InitializeComponent();
    }

    private async void button1_Click(object sender, EventArgs e) {
        var result = await FooAsync();
        label1.Text = result.ToString();
    }

    static async Task<int> FooAsync() {

        var t1 = Method1Async();
        var t2 = Method2Async();

        var result1 = await t1;
        var result2 = await t2;

        return result1 + result2;

    }

    static Task<int> Method1Async() {
        return Task.Run(
            () => {
                Thread.Sleep(3000);
                return 11;
            }
        );
    }

    static Task<int> Method2Async() {
        return Task.Run(
            () => {
                Thread.Sleep(5000);
                return 22;
            }
        );
    }

}

await2番目をFooAsyncに置き換えた場合t2.Result、ボタンをクリックしてから約3秒間応答し続け、その後約2秒間フリーズします。

  • 最初の後の継続はawait、UIスレッドでスケジュールされる順番を丁寧に待ちます。これは、Method1Async()タスクの終了後、つまり約3秒後に発生します。
  • その時点で、タスクが終了t2.Resultするまで、約2秒後にUIスレッドを無礼にブロックします。Method2Async()

asyncの前を削除して置き換えた場合、button1_Clickデッドロックが発生します。awaitFooAsync().Result

  • UIスレッドは、FooAsync()タスクが終了するのを待ちます。
  • 継続が終了するのを待つだろう、
  • UIスレッドが利用可能になるのを待ちます。
  • によってブロックされているため、そうではありませんFooAsync().Result

Stephen Toubによる「Await、SynchronizationContext、およびConsole Apps」という記事は、この主題を理解する上で非常に貴重でした。

于 2012-09-30T20:28:58.510 に答える