Programing

비동기 작업을 동 기적으로 대기하는 이유와 여기서 Wait ()가 프로그램을 정지시키는 이유

lottogame 2020. 3. 17. 08:29
반응형

비동기 작업을 동 기적으로 대기하는 이유와 여기서 Wait ()가 프로그램을 정지시키는 이유


서문 : 해결책이 아니라 설명을 찾고 있습니다. 나는 이미 해결책을 알고 있습니다.

태스크 기반 비동기 패턴 (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이 얼게하는 이유는 무엇입니까? 작업 개체에 문제가 있습니까? 명백한 교착 상태가 있습니까?


await비동기 방식의 내부는 UI 스레드에 다시 와서 노력하고있다.

UI 스레드가 전체 작업이 완료되기를 기다리는 중이므로 교착 상태가 있습니다.

비동기 호출을 이동하면 Task.Run()문제가 해결됩니다.
비동기 호출이 이제 스레드 풀 스레드에서 실행 중이므로 UI ​​스레드로 다시 돌아 가려고하지 않으므로 모든 것이 작동합니다.

또는 StartAsTask().ConfigureAwait(false)내부 작업을 대기하기 전에 호출 하여 교착 상태를 완전히 피하면서 UI 스레드가 아닌 스레드 풀로 돌아올 수 있습니다.


async동기 코드에서 코드를 호출하는 것은 매우 까다로울 수 있습니다.

내 블로그에서이 교착 상태에 대한 전체 이유를 설명합니다 . 간단히 말해서 각각의 시작 부분에 기본적으로 저장되고 await메소드를 재개하는 데 사용되는 "컨텍스트"가 있습니다.

따라서 이것이 UI 컨텍스트에서 호출 await되면 완료되면 async메소드는 해당 컨텍스트를 다시 입력하여 계속 실행합니다. 불행히도 Wait(또는 Result)를 사용하는 코드 는 해당 컨텍스트에서 스레드를 차단하므로 async메소드를 완료 할 수 없습니다.

이를 피하기위한 지침은 다음과 같습니다.

  1. ConfigureAwait(continueOnCapturedContext: false)가능한 많이 사용하십시오 . 이를 통해 async컨텍스트를 다시 입력하지 않고도 메소드를 계속 실행할 수 있습니다 .
  2. async끝까지 사용하십시오 . 또는 await대신에 사용하십시오 .ResultWait

메소드가 자연스럽게 비동기 인 경우 동기 래퍼를 노출시키지 않아야합니다 .


여기 내가 한 일이 있습니다

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 스레드를 차단하지 않음


작은 사용자 정의 동기화 컨텍스트를 사용하면 동기화 기능은 교착 상태를 만들지 않고 비동기 기능의 완료를 기다릴 수 있습니다. 다음은 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

참고 URL : https://stackoverflow.com/questions/14485115/synchronously-waiting-for-an-async-operation-and-why-does-wait-freeze-the-pro

반응형