비동기 작업을 동시에 기다리는 중이며 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
메서드를 완료할 수 없습니다.
이를 방지하기 위한 지침은 다음과 같습니다.
- 사용하다
ConfigureAwait(continueOnCapturedContext: false)
될 수 있는 대로이 기능을 사용하면async
컨텍스트를 다시 입력하지 않고 실행을 계속할 수 있습니다. - 사용하다
async
쭉요.사용하다await
에Result
또는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
'programing' 카테고리의 다른 글
MEF 플러그인 프로젝트에 참조를 추가할 때 경고 아이콘이 표시되는 이유는 무엇입니까? (0) | 2023.05.13 |
---|---|
Angular 2에서 구성 요소의 재렌더링을 강제하는 방법은 무엇입니까? (0) | 2023.05.13 |
파이썬에서 목록이 비어 있는지 확인하는 방법은 무엇입니까? (0) | 2023.05.13 |
C# Using block이란 무엇이며 왜 사용해야 합니까? (0) | 2023.05.13 |
명시적 SQL 조인과 암시적 SQL 조인 (0) | 2023.05.13 |