357

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

タスクベースの非同期パターン (TAP)、async、および await に関する 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

214

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

UI スレッドがタスク全体の完了を待っているため、デッドロックが発生しています。

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

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

于 2013-01-23T16:59:06.310 に答える
54

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

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

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

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

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

メソッドが自然に非同期である場合、(おそらく) 同期ラッパーを公開するべきではありません

于 2013-01-23T17:08:56.320 に答える
0

小さなカスタム同期コンテキストを使用すると、同期関数は、デッドロックを作成することなく、非同期関数の完了を待機できます。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
于 2016-09-27T13:27:06.827 に答える
-1

私にとって実際に最も効果的な解決策は次のとおりです。

AsyncMethod(<params>).ConfigureAwait(true).GetAwaiter().GetResult();

UI-Contentブロッキングやディスパッチャの問題がなく、CTOR からも動作します。

于 2021-06-18T16:27:25.913 に答える