私はしばらくの間この問題を回避してきましたが、今私は本当に何がうまくいかないのかを理解したいと思います。私はかなりシンプルなアプリケーションを持っています(これは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!
}
今私は非同期メソッドがあり、積極的にそれを待つのは奇妙だと理解していますが、なぜそれがデッドロックを生成するのですか?
あなたの問題は、_.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スレッドが現在ブロックされているという事実によってブロックされません。