[C#] 비동기 Task <T> 메서드를 어떻게 동 기적으로 실행합니까?

async / await에 대해 배우고 있으며 비동기 메서드를 동 기적으로 호출 해야하는 상황이 발생했습니다. 어떻게해야합니까?

비동기 방법 :

public async Task<Customers> GetCustomers()
{
    return await Service.GetCustomersAsync();
}

정상적인 사용법 :

public async void GetCustomers()
{
    customerList = await GetCustomers();
}

나는 다음을 사용하려고 시도했다.

Task<Customer> task = GetCustomers();
task.Wait()

Task<Customer> task = GetCustomers();
task.RunSynchronously();

Task<Customer> task = GetCustomers();
while(task.Status != TaskStatus.RanToCompletion)

나는 또한 여기 에서 제안을 시도했지만 디스패처가 일시 중단 된 상태에서는 작동하지 않습니다.

public static void WaitWithPumping(this Task task) 
{
        if (task == null) throw new ArgumentNullException(“task”);
        var nestedFrame = new DispatcherFrame();
        task.ContinueWith(_ => nestedFrame.Continue = false);
        Dispatcher.PushFrame(nestedFrame);
        task.Wait();
}

다음은 호출의 예외 및 스택 추적입니다 RunSynchronously.

System.InvalidOperationException

메시지 : 대리인에게 바인딩되지 않은 작업에서 RunSynchronously가 호출되지 않을 수 있습니다.

InnerException : null

출처 : mscorlib

StackTrace :

          at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler)
   at System.Threading.Tasks.Task.RunSynchronously()
   at MyApplication.CustomControls.Controls.MyCustomControl.CreateAvailablePanelList() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 638
   at MyApplication.CustomControls.Controls.MyCustomControl.get_AvailablePanels() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 233
   at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>b__36(DesktopPanel panel) in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 597
   at System.Collections.Generic.List`1.ForEach(Action`1 action)
   at MyApplication.CustomControls.Controls.MyCustomControl.<CreateOpenPanelList>d__3b.MoveNext() in C:\Documents and Settings\...\MyApplication.CustomControls\Controls\MyCustomControl.xaml.cs:line 625
   at System.Runtime.CompilerServices.TaskAwaiter.<>c__DisplayClass7.<TrySetContinuationForAwait>b__1(Object state)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.DispatcherOperation.InvokeImpl()
   at System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(Object state)
   at System.Threading.ExecutionContext.runTryCode(Object userData)
   at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
   at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Windows.Threading.DispatcherOperation.Invoke()
   at System.Windows.Threading.Dispatcher.ProcessQueue()
   at System.Windows.Threading.Dispatcher.WndProcHook(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndWrapper.WndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam, Boolean& handled)
   at MS.Win32.HwndSubclass.DispatcherCallbackOperation(Object o)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(Object source, Delegate method, Object args, Int32 numArgs, Delegate catchHandler)
   at System.Windows.Threading.Dispatcher.InvokeImpl(DispatcherPriority priority, TimeSpan timeout, Delegate method, Object args, Int32 numArgs)
   at MS.Win32.HwndSubclass.SubclassWndProc(IntPtr hwnd, Int32 msg, IntPtr wParam, IntPtr lParam)
   at MS.Win32.UnsafeNativeMethods.DispatchMessage(MSG& msg)
   at System.Windows.Threading.Dispatcher.PushFrameImpl(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   at System.Windows.Threading.Dispatcher.Run()
   at System.Windows.Application.RunDispatcher(Object ignore)
   at System.Windows.Application.RunInternal(Window window)
   at System.Windows.Application.Run(Window window)
   at System.Windows.Application.Run()
   at MyApplication.App.Main() in C:\Documents and Settings\...\MyApplication\obj\Debug\App.g.cs:line 50
   at System.AppDomain._nExecuteAssembly(RuntimeAssembly assembly, String[] args)
   at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
   at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
   at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
   at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
   at System.Threading.ThreadHelper.ThreadStart()



답변

다음은 모든 경우 (정지 된 발송자를 포함)에서 작동하는 해결 방법입니다. 내 코드가 아니며 여전히 완전히 이해하기 위해 노력하고 있지만 작동합니다.

다음을 사용하여 호출 할 수 있습니다.

customerList = AsyncHelpers.RunSync<List<Customer>>(() => GetCustomers());

코드는 여기에서

public static class AsyncHelpers
{
    /// <summary>
    /// Execute's an async Task<T> method which has a void return value synchronously
    /// </summary>
    /// <param name="task">Task<T> method to execute</param>
    public static void RunSync(Func<Task> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        synch.Post(async _ =>
        {
            try
            {
                await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();

        SynchronizationContext.SetSynchronizationContext(oldContext);
    }

    /// <summary>
    /// Execute's an async Task<T> method which has a T return type synchronously
    /// </summary>
    /// <typeparam name="T">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static T RunSync<T>(Func<Task<T>> task)
    {
        var oldContext = SynchronizationContext.Current;
        var synch = new ExclusiveSynchronizationContext();
        SynchronizationContext.SetSynchronizationContext(synch);
        T ret = default(T);
        synch.Post(async _ =>
        {
            try
            {
                ret = await task();
            }
            catch (Exception e)
            {
                synch.InnerException = e;
                throw;
            }
            finally
            {
                synch.EndMessageLoop();
            }
        }, null);
        synch.BeginMessageLoop();
        SynchronizationContext.SetSynchronizationContext(oldContext);
        return ret;
    }

    private class ExclusiveSynchronizationContext : SynchronizationContext
    {
        private bool done;
        public Exception InnerException { get; set; }
        readonly AutoResetEvent workItemsWaiting = new AutoResetEvent(false);
        readonly Queue<Tuple<SendOrPostCallback, object>> items =
            new Queue<Tuple<SendOrPostCallback, object>>();

        public override void Send(SendOrPostCallback d, object state)
        {
            throw new NotSupportedException("We cannot send to our same thread");
        }

        public override void Post(SendOrPostCallback d, object state)
        {
            lock (items)
            {
                items.Enqueue(Tuple.Create(d, state));
            }
            workItemsWaiting.Set();
        }

        public void EndMessageLoop()
        {
            Post(_ => done = true, null);
        }

        public void BeginMessageLoop()
        {
            while (!done)
            {
                Tuple<SendOrPostCallback, object> task = null;
                lock (items)
                {
                    if (items.Count > 0)
                    {
                        task = items.Dequeue();
                    }
                }
                if (task != null)
                {
                    task.Item1(task.Item2);
                    if (InnerException != null) // the method threw an exeption
                    {
                        throw new AggregateException("AsyncHelpers.Run method threw an exception.", InnerException);
                    }
                }
                else
                {
                    workItemsWaiting.WaitOne();
                }
            }
        }

        public override SynchronizationContext CreateCopy()
        {
            return this;
        }
    }
}


답변

알린다 이 대답은 세 가지 살입니다. 나는 주로 .Net 4.0에 대한 경험을 바탕으로 작성했으며 4.5에 대해서는 async-await. 일반적으로 말해서 그것은 훌륭한 간단한 해결책이지만 때로는 문제가 발생합니다. 의견에서 토론을 읽으십시오.

.Net 4.5

이것을 사용하십시오 :

// For Task<T>: will block until the task is completed...
var result = task.Result; 

// For Task (not Task<T>): will block until the task is completed...
task2.RunSynchronously();

참조 :
TaskAwaiter ,
Task.Result ,
Task.RunSynchronously


.Net 4.0

이것을 사용하십시오 :

var x = (IAsyncResult)task;
task.Start();

x.AsyncWaitHandle.WaitOne();

…아니면 이거:

task.Start();
task.Wait();


답변

아무도 이것을 언급하지 않았습니다.

public Task<int> BlahAsync()
{
    // ...
}

int result = BlahAsync().GetAwaiter().GetResult();

여기에 다른 방법 중 일부는 아니지만 다음과 같은 이점이 있습니다.

  • 그것은 예외 (같은 삼키는하지 않습니다 Wait)
  • 그것이 던져 예외를 포장하지 않을 것이다 AggregateException(같은 Result)
  • 모두를 위해 작동 Task하고 Task<T>( 자신을 밖으로 시도! )

또한 GetAwaiter오리 형식 이기 때문에 작업뿐만 아니라 비동기 메서드 ( ConfiguredAwaitable또는 같은 YieldAwaitable) 에서 반환되는 모든 개체에 대해서도 작동합니다 .


편집 : 기다릴 때마다 .Result추가하지 않는 한이 방법 (또는 )을 사용 하여 교착 상태에 빠질 .ConfigureAwait(false)수 있습니다 BlahAsync()(직접 호출하는 방법뿐만 아니라). 설명 .

// In BlahAsync() body
await FooAsync(); // BAD!
await FooAsync().ConfigureAwait(false); // Good... but make sure FooAsync() and
                                        // all its descendants use ConfigureAwait(false)
                                        // too. Then you can be sure that
                                        // BlahAsync().GetAwaiter().GetResult()
                                        // won't deadlock.

.ConfigureAwait(false)어디에나 추가하기에 너무 게으르고 성능에 신경 쓰지 않으면 대안으로 할 수 있습니다

Task.Run(() => BlahAsync()).GetAwaiter().GetResult()


답변

스케줄러가 동 기적으로 실행하도록 속이기보다는 스레드 풀에서 태스크를 실행하는 것이 훨씬 간단합니다. 그렇게하면 교착 상태가되지 않을 것입니다. 컨텍스트 전환으로 인해 성능에 영향을줍니다.

Task<MyResult> DoSomethingAsync() { ... }

// Starts the asynchronous task on a thread-pool thread.
// Returns a proxy to the original task.
Task<MyResult> task = Task.Run(() => DoSomethingAsync());

// Will block until the task is completed...
MyResult result = task.Result; 


답변

async / await에 대해 배우고 있으며 비동기 메서드를 동 기적으로 호출 해야하는 상황이 발생했습니다. 어떻게해야합니까?

가장 좋은 대답은 그렇지 은 “상황이”무엇에 의존 세부 사항.

getter / setter 속성입니까? 대부분의 경우 “비동기 속성”보다 비동기 메서드를 사용하는 것이 좋습니다. (자세한 내용 은 비동기 속성에 대한 내 블로그 게시물을 참조하십시오 ).

이 앱은 MVVM 앱이며 비동기 데이터 바인딩을 수행 하시겠습니까? 그런 다음 비동기 데이터 바인딩에 대한 MSDN 기사에서NotifyTask 설명한 것처럼 my와 같은 것을 사용 하십시오 .

생성자입니까? 그런 다음 비동기 팩토리 메소드를 고려할 수 있습니다. (자세한 내용 은 비동기 생성자에 대한 내 블로그 게시물을 참조하십시오 ).

비동기 동기화를 수행하는 것보다 거의 항상 더 나은 답변이 있습니다.

귀하의 상황에서 가능하지 않은 경우 (여기서 상황을 설명 하는 질문을 통해 이것을 알고 있음 ) 동기 코드를 사용하는 것이 좋습니다. 모든면에서 비동기가 가장 좋습니다. 끝까지 동기화하는 것이 가장 좋습니다. 비동기 동기화는 권장되지 않습니다.

그러나 비동기 동기화가 필요한 몇 가지 상황이 있습니다. 특히, 당신은 당신이 너무 호출 코드에 의해 제약을 동기화 될 (그리고 절대적으로 방법이 없습니다 다시 생각 또는 재 구조 비동기를 허용하는 코드), 그리고 당신이 코드를 비동기 호출. 이것은 매우 드문 상황이지만 때때로 나타납니다.

이 경우 브라운 필드 async개발 에 대한 기사에서 설명한 해킹 중 하나 , 특히 다음 을 사용해야 합니다.

  • 차단 (예 🙂 GetAwaiter().GetResult(). 참고 이 교착 상태가 발생할 수 있습니다 (I 내 블로그에 설명 참조).
  • 스레드 풀 스레드에서 코드를 실행 (예 Task.Run(..).GetAwaiter().GetResult()). 이것은 비동기 코드가 스레드 풀 스레드에서 실행될 수있는 경우에만 작동합니다 (즉, UI 또는 ASP.NET 컨텍스트에 종속되지 않음).
  • 중첩 된 메시지 루프. 이것은 비동기 코드가 특정 컨텍스트 유형이 아닌 단일 스레드 컨텍스트만을 가정하는 경우에만 작동합니다 (많은 UI 및 ASP.NET 코드는 특정 컨텍스트를 예상 함).

중첩 된 메시지 루프는 모든 해킹 중 가장 위험 합니다. 재진입 이 발생하기 때문 입니다. 재진입은 추론하기가 매우 까다 롭고 (IMO)는 Windows에서 대부분의 응용 프로그램 버그의 원인입니다. 특히, UI 스레드에 있고 작업 대기열을 차단하면 (비동기 작업이 완료되기를 기다리는 중) CLR이 실제로 일부 메시지 펌핑을 수행합니다. 실제로 내부에서 일부 Win32 메시지 처리 합니다 코드 . 아, 그리고 당신이 메시지를 아무 생각이 없다 – 크리스 Brumme가 말한다 “?겠습니까하지가 펌핑 얻을 것이다 정확히 알고 좋은 일을 불행하게도, 펌핑은 인간의 이해를 넘어 검은 예술이다.” 우리는 정말로 알 희망이 없습니다.

따라서 UI 스레드에서 이와 같이 차단하면 문제가 발생합니다. 같은 기사에서 인용 한 또 다른 인용문 : “때때로, 회사 내부 또는 외부의 고객이 STA [UI 스레드]에서 관리 차단 중에 메시지를 펌핑하고 있음을 발견했습니다. 이는 매우 어려운 일이므로 합법적 인 문제입니다. 재진입에 강인한 코드를 작성해야합니다. “

그렇습니다. 재진입에 강인한 코드를 작성하는 것은 매우 어렵다. 또한 중첩 된 메시지 루프를 사용 하면 재진입시 강력한 코드를 작성할 수 있습니다. 이유는 이 질문에 대한 허용 (그리고 가장 upvoted) 답변 입니다 매우 위험 연습한다.

다른 모든 옵션을 완전히 벗어난 경우-코드를 다시 디자인 할 수없고 코드를 비동기식으로 재구성 할 수 없습니다. 변경 불가능한 호출 코드를 강제로 동기화해야합니다.-다운 스트림 코드를 동기화하도록 변경할 수 없습니다 – 다음 – 별도의 스레드에서 비동기 코드를 실행할 수 없습니다 – 당신은 차단할 수 없습니다 만 다음 당신은 재진입을 수용 고려해야한다.

이 코너에서 자신을 찾으면 Dispatcher.PushFrameWPF 앱 과 같은 것을 사용하고 , Application.DoEventsWinForm 앱 과 함께 반복하고 , 일반적인 경우에는 내 자신의 것을 사용하는 것이 좋습니다 AsyncContext.Run.


답변

귀하의 질문을 올바르게 읽고 있다면 비동기 메소드에 대한 동기 호출을 원하는 코드가 일시 중단 된 디스패처 스레드에서 실행되고 있습니다. 그리고 비동기 메소드가 완료 될 때까지 실제로 해당 스레드를 동 기적으로 차단 하려고합니다 .

C # 5의 비동기 메소드는 효과적으로 후드 아래에서 메소드를 잘게 자르고 Task전체 shabang의 전체 완료를 추적 할 수있는를 리턴하여 구동됩니다. 그러나 잘린 메소드가 실행되는 방식은 await연산자에 전달 된 표현식의 유형에 따라 다릅니다 .

대부분의 경우 await유형 표현식을 사용하게 됩니다 Task. await패턴 의 Task 구현 은 “smart” SynchronizationContext이며 이는 기본적으로 다음을 발생시킵니다.

  1. 를 입력하는 스레드 await가 디스패처 또는 WinForms 메시지 루프 스레드에 있으면 비동기 메소드의 청크가 메시지 큐 처리의 일부로 발생하는지 확인합니다.
  2. 에 들어가는 스레드 await가 스레드 풀 스레드에있는 경우 비동기 메소드의 나머지 청크는 스레드 풀의 어느 곳에서나 발생합니다.

그렇기 때문에 비동기 메소드 구현이 일시 중지되었지만 Dispatcher에서 나머지를 실행하려고합니다.

…. 백업! ….

질문을해야합니다. 비동기 메소드를 동 기적으로 차단하려고합니까? 그렇게하면 왜 메소드가 비동기 적으로 호출되기를 원하는지에 대한 목적을 상실하게됩니다. 일반적으로 awaitDispatcher 또는 UI 메소드에서 사용 을 시작하면 전체 UI 플로우를 비동기로 설정해야합니다. 예를 들어 콜 스택이 다음과 같은 경우 :

  1. [상단] WebRequest.GetResponse()
  2. YourCode.HelperMethod()
  3. YourCode.AnotherMethod()
  4. YourCode.EventHandlerMethod()
  5. [UI Code].Plumbing()WPF또는 WinForms코드
  6. [메시지 루프]WPF또는 WinForms메시지 루프

그런 다음 코드가 비동기를 사용하도록 변환되면 일반적으로

  1. [상단] WebRequest.GetResponseAsync()
  2. YourCode.HelperMethodAsync()
  3. YourCode.AnotherMethodAsync()
  4. YourCode.EventHandlerMethodAsync()
  5. [UI Code].Plumbing()WPF또는 WinForms코드
  6. [메시지 루프]WPF또는 WinForms메시지 루프

실제로 응답

위의 AsyncHelpers 클래스는 중첩 된 메시지 루프처럼 작동하기 때문에 실제로 작동하지만 Dispatcher 자체에서 실행하지 않고 Dispatcher에 자체 병렬 메커니즘을 설치합니다. 그것은 당신의 문제에 대한 하나의 해결 방법입니다.

또 다른 해결 방법은 스레드 풀 스레드에서 비동기 메소드를 실행 한 다음 완료 될 때까지 기다리는 것입니다. 그렇게하는 것은 쉽습니다-다음 코드 조각으로 할 수 있습니다 :

var customerList = TaskEx.RunEx(GetCustomers).Result;

최종 API는 Task.Run (…)이지만 CTP에는 Ex 접미사가 필요합니다 ( 여기 설명 ).


답변

이것은 나를 위해 잘 작동합니다

public static class TaskHelper
{
    public static void RunTaskSynchronously(this Task t)
    {
        var task = Task.Run(async () => await t);
        task.Wait();
    }

    public static T RunTaskSynchronously<T>(this Task<T> t)
    {
        T res = default(T);
        var task = Task.Run(async () => res = await t);
        task.Wait();
        return res;
    }
}