1. ホーム
  2. c#

[解決済み】非同期処理の待ち時間、Wait()でプログラムがフリーズする原因はここにある

2022-03-25 09:15:29

質問

前置き : 解決策だけでなく、説明を求めています。解決策はもう知っている。

タスクベース非同期パターン(TAP)、async、awaitに関するMSDN記事を数日かけて勉強したにもかかわらず、細かい部分でまだ少し混乱しています。

Windows Store Apps用のロガーを書いているのですが、非同期と同期の両方のロギングをサポートしたいのです。非同期メソッドはTAPに従いますが、同期メソッドはこれらをすべて隠して、普通のメソッドのように見え、動作する必要があります。

これが非同期ロギングのコアとなるメソッドです。

private async Task WriteToLogAsync(string text)
{
    StorageFolder folder = ApplicationData.Current.LocalFolder;
    StorageFile file = await folder.CreateFileAsync("log.log",
        CreationCollisionOption.OpenIfExists);
    await FileIO.AppendTextAsync(file, text,
        Windows.Storage.Streams.UnicodeEncoding.Utf8);
}

では、それに対応するsynchronousメソッドを...

バージョン1 :

private void WriteToLog(string text)
{
    Task task = WriteToLogAsync(text);
    task.Wait();
}

これは正しく見えますが、うまくいきません。プログラム全体が永遠にフリーズしてしまいます。

バージョン2 :

うーん、もしかしてタスクが開始されていないのでは?

private void WriteToLog(string text)
{
    Task task = WriteToLogAsync(text);
    task.Start();
    task.Wait();
}

これは、次のように投げます。 InvalidOperationException: Start may not be called on a promise-style task.

バージョン3です。

うーん... Task.RunSynchronously は期待できそうですね。

private void WriteToLog(string text)
{
    Task task = WriteToLogAsync(text);
    task.RunSynchronously();
}

これは、次のように投げます。 InvalidOperationException: RunSynchronously may not be called on a task not bound to a delegate, such as the task returned from an asynchronous method.

バージョン4(解決策)。

private void WriteToLog(string text)
{
    var task = Task.Run(async () => { await WriteToLogAsync(text); });
    task.Wait();
}

これはうまくいく。つまり、2と3は間違ったツールなのです。でも1は?1のどこが悪くて、4と何が違うの?1がフリーズする原因は何でしょうか?タスクオブジェクトに何か問題があるのでしょうか?明らかでないデッドロックがあるのでしょうか?

解決方法は?

その await 非同期メソッドの内部では、UIスレッドに戻ろうとしています。

UIスレッドはタスク全体の完了を待つのに忙しいので、デッドロックが発生します。

非同期呼び出しを Task.Run() は問題を解決します。

非同期呼び出しがスレッドプールスレッドで実行されるようになったため、UIスレッドに戻ろうとせず、したがってすべてが動作します。

別の方法として StartAsTask().ConfigureAwait(false) 内側の操作を待つ前に、UI スレッドではなくスレッドプールに戻ってくるようにし、デッドロックを完全に回避しています。