programing

비동기 작업을 동시에 기다리는 중이며 Wait()이 여기서 프로그램을 중지하는 이유는 무엇입니까?

topblog 2023. 5. 13. 08:42
반응형

비동기 작업을 동시에 기다리는 중이며 Wait()이 여기서 프로그램을 중지하는 이유는 무엇입니까?

서문:저는 단순한 해결책이 아닌 설명을 찾고 있습니다.저는 이미 해결책을 알고 있습니다.

작업 기반 비동기 패턴(TAP), 비동기 및 대기에 대한 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);
}

이제 해당 동기식 방법은...

버전 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 쭉요.사용하다awaitResult또는Wait.

메서드가 자연스럽게 비동기식이면 동기식 래퍼를 노출하지 않아야 합니다.

여기 제가 한 일이 있습니다.

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

저에게 가장 효과적인 솔루션은 다음과 같습니다.

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

또한 작동합니다.UI-Content차단 및 디스패처 문제 없이, 그리고 CTOR의 문제에서도.

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

반응형