비동기 작업을 동 기적으로 대기하는 이유와 여기서 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

3
@amadib, ver.1과 4는 [rpvided answers. Ver.2 anв 3 이미 시작된 작업을 다시 시작해보십시오. 질문을 게시하십시오. 당신이 .NET 4.0 .NET 4.5 비동기 / await를 문제가있을 수 있는지 불분명하다
나디 Vanin Геннадий Ванин

1
버전 4는 Xamarin Forms에 가장 적합한 옵션입니다. 우리는 나머지 옵션을 시험해 보았지만 모든 경우에 교착 상태가 발생하지는 않았습니다.
Ramakrishna

감사! 버전 4가 나를 위해 일했습니다. 그러나 여전히 비동기 적으로 실행됩니까? 비동기 키워드가 있기 때문에 가정합니다.
sshirley

답변:


189

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

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

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

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


9
+1. 여기에 또 하나의 설명이 있습니다 -Await, UI, 교착 상태! 어머!
Alexei Levenkov

13
ConfigureAwait(false)이 경우에는 적절한 해결책이다. 캡처 된 컨텍스트에서 콜백을 호출 할 필요가 없으므로 그렇게해서는 안됩니다. API 메소드이기 때문에 모든 호출자가 UI 컨텍스트 밖으로 이동하지 않고 내부적으로 처리해야합니다.
Servy

@Servy Am은 ConfigureAwait를 언급 한 이후 묻습니다. .net3.5를 사용하고 있으며 사용중인 비동기 라이브러리에서 사용할 수 없었던 configure await cos를 제거해야했습니다. 직접 작성하거나 비동기 호출을 기다리는 다른 방법이 있습니까? 왜냐하면 내 방법도 중단됩니다. Task는 없지만 Task.Run은 없습니다. 이것은 아마도 그 자체로 질문이 될 것입니다.
flexxxit

@flexxxit :을 사용해야합니다 Microsoft.Bcl.Async.
SLaks

48

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

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

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

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

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

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


catch ()에서 비동기 작업을 실행해야하는데,이 작업을 수행하는 async방법을 지원하지 않으며 화재를 예방하고 상황을 잊어 버립니다.
Zapnologica

1
@Zapnologica : VS2015부터 블록 단위로 await지원됩니다 catch. 이전 버전 인 경우 예외를 로컬 변수에 할당하고 awaitafter 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

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