web-dev-qa-db-ja.com

Winformsが非同期メソッドを呼び出すとプログラムがハングアップする

私はしばらくの間この問題を回避してきましたが、今私は本当に何がうまくいかないのかを理解したいと思います。私はかなりシンプルなアプリケーションを持っています(これはyoutrackのタートルイズSVNプラグインですが、簡単なwinformsアプリで問題を再現できます)。

非同期メソッドがあるResolveIssue

public async Task<bool> ResolveIssue(Issue issue, int revision, string[] pathList)
{
    await Task.Delay(1000);

    return true;
}

デッドロックを作成するために私がしなければならないのは、この非同期メソッドをButtonイベントハンドラーで呼び出し、Task.WaitまたはTask.Resultを呼び出すだけです。

private void buttonOk_Click(object sender, System.EventArgs e)
{
    var asyncResolvedIssue = api.ResolveIssue(issue, revision, pathList);
    if (asyncResolvedIssue.Result) {} // <== deadlock!
}

今私は非同期メソッドがあり、積極的にそれを待つのは奇妙だと理解していますが、なぜそれがデッドロックを生成するのですか?

31
bas

あなたの問題は、_.Result_を呼び出すときにUIスレッドをブロックしていて、_Task.Delay_の後の継続にUIスレッドで実行するように指示したためです。つまり、UIがフリーになるのを待機するときにブロックされるタスクを待機するUIをブロックしています。これは、古典的なデッドロックです。

2つのソリューション。まず、ボタンも非同期にクリックします。

_private async void buttonOk_Click(object sender, System.EventArgs e)
{
    var asyncResolvedIssue = api.ResolveIssue(issue, revision, pathList);
    if (await asyncResolvedIssue) {} // <== no deadlock!
}
_

イベントハンドラは、_async void_を実行できる唯一の場所です。

他のオプションは、_Task.Delay_に伝える ConfigureAwait(bool) をfalseに設定することで、残りの関数をUIスレッドで実行する必要がないことを伝えます。

_public async Task<bool> ResolveIssue(Issue issue, int revision, string[] pathList)
{
    await Task.Delay(1000).ConfigureAwait(false);

    return true;
}
_

これで、_Task.Delay_の後のコード行は、UIスレッドではなくスレッドプールスレッドで実行され、UIスレッドが現在ブロックされているという事実によってブロックされません。

43