3

次のコードで正常に動作しています。

private void button1_Click(object sender, EventArgs e)
{
    Task<int> t = test(5);
    Console.WriteLine(t.Result);
}

private Task<int> test(int n)
{
    return Task.Run(() => 
    {
        return n;
    });
}

しかし、非同期メソッドでテスト メソッドをラップすると、機能しません。

private Task<int> test(int n)
{
    return Task.Run(() =>
    {
        return n;
    });
}

public async Task<int> wrap()
{
    return await test(5);
}

private void button1_Click(object sender, EventArgs e)
{
    Task<int> t = wrap();
    Console.WriteLine(t.Result);
}

フォームは応答を失います。await を使用すると、期待どおりに動作します。

Update1: これら 2 つの回答はどちらも正しいですが、回答としてマークできるのは 1 つだけです。この質問の理解に基づいて、さらにテストを行いました。ラップ メソッドで ConfigureAwait を使用して、継続を UI 以外のスレッドで実行するようにしました。

public async Task<int> wrap()
{
    return await test(5).ConfigureAwait(false);
}

それは正常に動作します。そして、私はこれをテストしました:

public async Task<int> wrap()
{
    int i = await test(5).ConfigureAwait(false);
    int j = i + await test(3);
    return j;
}

ボタンをクリックしたときは初めて動作しますが、2回目のクリックで再びデッドロックします。test(3) の後に ConfigureAwait(false) を追加すると、次のようになります。

public async Task<int> wrap()
{
    int i = await test(5).ConfigureAwait(false);
    int j = i + await test(3).ConfigureAwait(false);
    return j;
}

再び機能していますが、これは私には意味がありません。最初の ConfigureAwait(false) のため、wrap() の後続の同期部分はすべて非 UI スレッドで実行する必要があります。2 番目の ConfigureAwait(false) が必要な理由がわかりません。

アップデート2:

private Task<int> test(int n)
{
    return Task.Run(() =>
    {
        Console.WriteLine("test(" + n + "): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
        return n;
    });
}

public async Task<int> wrap()
{
    Console.WriteLine("1.wrap(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
    int i = await test(5).ConfigureAwait(false);
    Console.WriteLine("2.wrap(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
    int j = i + await test(3);
    Console.WriteLine("3.wrap(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
    return j;
}

private void button1_Click(object sender, EventArgs e)
{
    try
    {
        Console.WriteLine("1.button1_Click(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);
        var t = wrap();
        Console.WriteLine("2.button1_Click(): " + System.Threading.Thread.CurrentThread.ManagedThreadId);

        Console.WriteLine(t.Result);    
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }    
}

数回クリックすると、フォームがフリーズし、出力は次のようになります。

1.button1_Click(): 8
1.wrap(): 8 
test(5): 13
2.wrap(): 8
2.button1_Click(): 8 
test(3): 13

驚いたことに、「2.wrap():」は「test(5)」ではなく「1.wrap():」と同じスレッドで実行されています。ConfigureAwait(false) の後のコードも UI スレッドにジャンプできるようです.</p>

4

2 に答える 2

4

これはデッドロックTaskです。シングルスレッド スケジューラで実行されている 2 つの が、互いの完了を待機しています。

これを理解するには、次の 2 つの非常に重要な点があります。

  • デフォルトでは、 の結果はawait開始時と同じスケジューラーに返されます。
  • UI はシングルスレッドのイベント ループで実行されawait、UI 呼び出しで使用できる即時スケジューラは、このイベント ループにディスパッチされるものです。このスケジューラで一度に実行できるタスクは 1 つだけです。

このため、UI のスケジューラ内で実行される でブロッキング メソッドを呼び出す方法には特に注意する必要があります。Task

2番目の例では、への呼び出しResultは、継続セットアップがwrap完了するのを待っています。継続は UI スレッドで実行されるようにスケジュールされていますが、呼び出しがResultたまたまブロックされているため、どちらも完了しません。

于 2013-08-24T04:27:21.683 に答える
3

あなたは私のブログで詳しく説明しているデッドロックを引き起こしています。要約すると、awaitキーワードは (デフォルトで) の前に現在のコンテキストをキャプチャし、そのコンテキスト内でawait残りのasyncメソッドを再開します。この場合、その「コンテキスト」は UI スレッドでコードを実行する UI コンテキストです。したがって、コードが を呼び出すとResult、UI スレッドがブロックされ、 がawait完了すると、UI スレッドで残りを実行できなくなりwrapます。したがって、デッドロック。

このデッドロックを回避する最善の方法は、await代わりに、ResultまたはWait非同期タスクに対して使用することです。つまり、クリック方法を次のように変更します。

private async void button1_Click(object sender, EventArgs e)
{
  Task<int> t = wrap();
  Console.WriteLine(await t);
}
于 2013-08-24T04:27:00.437 に答える