非同期操作を同期的に待機し、Wait()がプログラムをここでフリーズする理由


318

序文:単なる解決策ではなく、説明を求めています。私はすでに解決策を知っています。

タスクベースの非同期パターン(TAP)、非同期、待機に関するMSDNの記事の調査に数日費やしましたが、細かい詳細についてはまだ混乱しています。

私はWindowsストアアプリ用のロガーを書いており、非同期と同期の両方のロギングをサポートしたいと考えています。非同期メソッドは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);
}

今、対応する同期メソッド...

バージョン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をフリーズさせるのですか?タスクオブジェクトに問題がありますか?非自明のデッドロックはありますか?


他の場所で説明を得ている運はありますか?以下の答えは実際には洞察を提供しません。私は実際に4.5 / 5ではなく.net 4.0を使用しているため、一部の操作を使用できませんが、同じ問題が発生しています。
amadib 2013年

3
@ amadib、ver.1および4は[rpvided answer。Ver.2anв3は、既に開始されているタスクを再開しようとします。質問を投稿してください。.NET 4.0で.NET 4.5の非同期/
待機の

1
Xamarinフォームにはバージョン4が最適です。私たちは残りのオプションを試してみましたが、すべてのケースで機能せず、デッドロックを経験しました
Ramakrishna

ありがとう!バージョン4がうまくいきました。しかし、それでも非同期で実行されますか?asyncキーワードが存在するため、私はそう想定しています。
sshirley 2017

回答:


189

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

UIスレッドはタスク全体が完了するのを待機してビ​​ジーであるため、デッドロックが発生します。

非同期呼び出しを移動しTask.Run()て問題を解決します。
非同期呼び出しがスレッドプールスレッドで実行されているため、UIスレッドに戻ろうとせず、すべてが機能します。

または、StartAsTask().ConfigureAwait(false)内部操作を待機する前に呼び出して、UIスレッドではなくスレッドプールに戻るようにして、デッドロックを完全に回避することもできます。



13
ConfigureAwait(false)この場合、適切なソリューションです。キャプチャされたコンテキストでコールバックを呼び出す必要がないため、呼び出さないでください。APIメソッドであるため、すべての呼び出し元がUIコンテキストから移動するのではなく、内部的に処理する必要があります。
サービー2013年

@Servy ConfigureAwaitについて言及したので質問しています。私は.net3.5を使用しており、私が使用していた非同期ライブラリでは利用できなかった構成待機待ち行列を削除する必要がありました。自分で書く方法または非同期呼び出しを待つ別の方法はありますか?私のメソッドもハングします。TaskはありませんがTask.Runはありません。これはおそらくそれ自体の問題でしょう。
flexxxit 2017

@flexxxit:を使用する必要がありますMicrosoft.Bcl.Async
SLaks

48

async同期コードからコードを呼び出すのは非常に難しい場合があります。

私が説明私のブログでこのデッドロックのための完全な理由。つまり、それぞれの最初にデフォルトで保存されawait、メソッドを再開するために使用される「コンテキスト」があります。

したがって、これがUIコンテキストで呼び出された場合、がawait完了すると、asyncメソッドはそのコンテキストに再び入り、実行を継続しようとします。残念ながら、Wait(またはResult)を使用するコードはそのコンテキストでスレッドをブロックするため、asyncメソッドを完了できません。

これを回避するためのガイドラインは次のとおりです。

  1. ConfigureAwait(continueOnCapturedContext: false)できるだけ使用してください。これにより、asyncメソッドはコンテキストを再入力することなく実行を継続できます。
  2. asyncずっと使ってください。またはのawait代わりに使用します。ResultWait

メソッドが自然に非同期である場合、(おそらく)同期ラッパーを公開しないでください


私は、catch()で非同期タスクを実行する必要があります。catch()asyncは、これを行う方法をサポートしておらず、火災や忘却の状況を防止します。
Zapnologica

1
@Zapnologica:VS2015以降awaitcatchブロックでサポートされています。古いバージョンを使用している場合は、ローカル変数に例外を割り当てawait、catchブロックの後に実行できます
Stephen Cleary

5

これが私がやったことです

private void myEvent_Handler(object sender, SomeEvent e)
{
  // I dont know how many times this event will fire
  Task t = new Task(() =>
  {
    if (something == true) 
    {
        DoSomething(e);  
    }
  });
  t.RunSynchronously();
}

UIスレッドをブロックせずに機能する


0

小さなカスタム同期コンテキストを使用すると、sync関数は、デッドロックを作成することなく、非同期関数の完了を待機できます。以下は、WinFormsアプリの小さな例です。

Imports System.Threading
Imports System.Runtime.CompilerServices

Public Class Form1

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        SyncMethod()
    End Sub

    ' waiting inside Sync method for finishing async method
    Public Sub SyncMethod()
        Dim sc As New SC
        sc.WaitForTask(AsyncMethod())
        sc.Release()
    End Sub

    Public Async Function AsyncMethod() As Task(Of Boolean)
        Await Task.Delay(1000)
        Return True
    End Function

End Class

Public Class SC
    Inherits SynchronizationContext

    Dim OldContext As SynchronizationContext
    Dim ContextThread As Thread

    Sub New()
        OldContext = SynchronizationContext.Current
        ContextThread = Thread.CurrentThread
        SynchronizationContext.SetSynchronizationContext(Me)
    End Sub

    Dim DataAcquired As New Object
    Dim WorkWaitingCount As Long = 0
    Dim ExtProc As SendOrPostCallback
    Dim ExtProcArg As Object

    <MethodImpl(MethodImplOptions.Synchronized)>
    Public Overrides Sub Post(d As SendOrPostCallback, state As Object)
        Interlocked.Increment(WorkWaitingCount)
        Monitor.Enter(DataAcquired)
        ExtProc = d
        ExtProcArg = state
        AwakeThread()
        Monitor.Wait(DataAcquired)
        Monitor.Exit(DataAcquired)
    End Sub

    Dim ThreadSleep As Long = 0

    Private Sub AwakeThread()
        If Interlocked.Read(ThreadSleep) > 0 Then ContextThread.Resume()
    End Sub

    Public Sub WaitForTask(Tsk As Task)
        Dim aw = Tsk.GetAwaiter

        If aw.IsCompleted Then Exit Sub

        While Interlocked.Read(WorkWaitingCount) > 0 Or aw.IsCompleted = False
            If Interlocked.Read(WorkWaitingCount) = 0 Then
                Interlocked.Increment(ThreadSleep)
                ContextThread.Suspend()
                Interlocked.Decrement(ThreadSleep)
            Else
                Interlocked.Decrement(WorkWaitingCount)
                Monitor.Enter(DataAcquired)
                Dim Proc = ExtProc
                Dim ProcArg = ExtProcArg
                Monitor.Pulse(DataAcquired)
                Monitor.Exit(DataAcquired)
                Proc(ProcArg)
            End If
        End While

    End Sub

     Public Sub Release()
         SynchronizationContext.SetSynchronizationContext(OldContext)
     End Sub

End Class
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.