同步等待异步操作,以及为什么等待()这里冻结程序(Synchronously waiting for

2019-07-18 15:01发布

前言 :我在寻找一个解释,而不仅仅是一个解决方案。 我已经知道了解决方案。

尽管已经花了几天时间研究有关基于任务的异步模式(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引起冻结? 有一些问题与任务对象? 是否有一个不明显的僵局?

Answer 1:

await你的异步方法内试图回到UI线程。

由于UI线程繁忙等待整个任务的完成,你有一个僵局。

移动异步调用Task.Run()解决了这个问题。
由于异步调用现在在一个线程池线程运行时,它并不试图回来到UI线程,因此,一切正常。

或者,你可以调用StartAsTask().ConfigureAwait(false) ,等待内部操作,使其回到线程池,而不是UI线程,完全避免僵局之前。



Answer 2:

调用async从同步码代码可以是相当棘手。

我解释了这个僵局在我的博客充分的理由 。 总之,有一个“上下文”,即在默认情况下,在每个月初保存await和用于恢复的方法。

所以,如果这就是所谓的在UI方面,当await完成, async方法试图重新进入这一背景下继续执行。 不幸的是,使用代码Wait (或Result )将阻止在这方面一个线程,所以async方法无法完成。

为了避免这种情况的指导方针是:

  1. 使用ConfigureAwait(continueOnCapturedContext: false)尽可能。 这使你的async方式继续,而无需重新输入上下文中执行。
  2. 使用async一路。 使用await ,而不是ResultWait

如果你的方法自然是异步的,那么你(可能)不应该暴露一个同步封装 。



Answer 3:

下面是我做的

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线程



Answer 4:

随着小自定义同步情况下,同步功能可以等待异步函数的完成,而不产生死锁。 下面是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


文章来源: Synchronously waiting for an async operation, and why does Wait() freeze the program here