C #의 동기 메서드에서 비동기 메서드를 호출하는 방법은 무엇입니까?


862

나는이 public async void Foo()나는 동기 방법에서 호출하려는 방법을. 지금까지 MSDN 설명서에서 본 모든 것은 비동기 메서드를 통해 비동기 메서드를 호출하는 것이지만 내 전체 프로그램은 비동기 메서드로 작성되지 않았습니다.

이것도 가능합니까?

다음은 비동기 메소드에서 이러한 메소드를 호출하는 예입니다. http://msdn.microsoft.com/en-us/library/hh300224(v=vs.110).aspx

이제 동기화 메소드에서 이러한 비동기 메소드를 호출하려고합니다.


2
나는 이것도 만났다. RoleProvider를 재정의하면 GetRolesForUser 메서드의 메서드 서명을 변경할 수 없으므로 메서드를 비동기로 만들 수 없으므로 await를 사용하여 비동기 적으로 api를 호출 할 수 없습니다. 내 임시 솔루션은 일반 HttpClient 클래스에 동기 메서드를 추가하는 것이 가능하지만 이것이 가능한지 여부와 그 의미를 알고 싶습니다.
Timothy Lee Russell 4

1
async void Foo()메소드가 a를 리턴하지 않기 때문에 Task호출자가 완료 시점을 알 수 없다는 것을 의미하므로 Task대신 리턴해야합니다 .
Dai

1
UI 스레드 에서이 작업을 수행하는 방법에 대한 관련 q / a 연결
noseratio

답변:


711

비동기식 프로그래밍은 코드베이스를 통해 "성장"합니다. 좀비 바이러스와 비교 되었습니다 . 가장 좋은 해결책은 그것이 자라도록하는 것이지만 때로는 불가능합니다.

Nito.AsyncEx 라이브러리에 부분적으로 비동기 코드 기반을 처리하기 위해 몇 가지 유형을 작성했습니다 . 그러나 모든 상황에서 작동하는 솔루션은 없습니다.

해결책 A

컨텍스트와 다시 동기화 할 필요가없는 간단한 비동기 메소드가있는 경우 다음을 사용할 수 있습니다 Task.WaitAndUnwrapException.

var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();

당신은 할 수 없습니다 사용하려는 Task.Wait또는 Task.Result그들이에 예외를 포장하기 때문에 AggregateException.

이 솔루션은 MyAsyncMethod컨텍스트와 다시 동기화되지 않는 경우에만 적합합니다 . 다시 말해서 모든 awaitMyAsyncMethod끝나야합니다 ConfigureAwait(false). 즉, UI 요소를 업데이트하거나 ASP.NET 요청 컨텍스트에 액세스 할 수 없습니다.

솔루션 B

MyAsyncMethod컨텍스트와 다시 동기화해야하는 경우 AsyncContext.RunTask중첩 된 컨텍스트를 제공하는 데 사용할 수 있습니다 .

var result = AsyncContext.RunTask(MyAsyncMethod).Result;

* 2014 년 4 월 14 일 업데이트 : 최신 버전의 라이브러리에서 API는 다음과 같습니다.

var result = AsyncContext.Run(MyAsyncMethod);

( 예외 를 전파하므로이 Task.Result예제에서 사용 하는 것이 좋습니다 ).RunTaskTask

AsyncContext.RunTask대신에 필요한 이유 Task.WaitAndUnwrapException는 WinForms / WPF / SL / ASP.NET에서 발생할 수있는 약간의 교착 상태 가능성 때문입니다.

  1. 동기 메서드는 async 메서드를 호출하여를 얻습니다 Task.
  2. 동기 메소드는에서 블로킹 대기를 수행합니다 Task.
  3. async방법은 await없이 사용합니다 ConfigureAwait.
  4. Task때만 완료하기 때문에이 상황에서 완료 할 수 없습니다 async방법은 완성입니다 에 async대한 연속을 예약하려고 시도하기 때문에이 메서드를 완료 할 수 없으며 SynchronizationContext동기식 메서드가 해당 컨텍스트에서 이미 실행 중이므로 WinForms / WPF / SL / ASP.NET에서 연속을 실행할 수 없습니다.

이것이 ConfigureAwait(false)모든 async방법 내에서 가능한 한 많이 사용하는 것이 좋은 이유 입니다.

솔루션 C

AsyncContext.RunTask모든 시나리오에서 작동하지는 않습니다. 예를 들어, async메소드가 UI 이벤트를 완료해야하는 무언가를 기다리는 경우 중첩 된 컨텍스트에서도 교착 상태가 발생합니다. 이 경우 async스레드 풀 에서 메소드를 시작할 수 있습니다.

var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

그러나이 솔루션에는 MyAsyncMethod스레드 풀 컨텍스트에서 작동 하는 솔루션이 필요합니다 . 따라서 UI 요소를 업데이트하거나 ASP.NET 요청 컨텍스트에 액세스 할 수 없습니다. 이 경우 ConfigureAwait(false)해당 await설명을 추가 하고 솔루션 A를 사용할 수 있습니다.

2019 년 5 월 1 일 업데이트 : 최신 "최악의 사례"는 MSDN 기사 here에 있습니다 .


9
솔루션 A는 내가 원하는 것처럼 보이지만 task.WaitAndUnwrapException ()은 .Net 4.5 RC로 만들지 않았습니다. task.Wait () 만 있습니다. 새 버전으로이 작업을 수행하는 방법을 알고 있습니까? 아니면 이것이 사용자 정의 확장 방법입니까?
deadlydog

3
WaitAndUnwrapExceptionAsyncEx 라이브러리 에서 내 자신의 방법입니다 . 공식 .NET 라이브러리는 동기화 코드와 비동기 코드를 혼합하는 데 큰 도움이되지 않습니다 (일반적으로 그렇게해서는 안됩니다!). 4.5에서 실행되도록 AsyncEx를 업데이트하기 전에 .NET 4.5 RTW와 XP가 아닌 새로운 랩톱을 기다리고 있습니다 (몇 주 동안 XP에 갇혀 있기 때문에 현재 4.5로 개발할 수 없습니다).
Stephen Cleary

12
AsyncContext이제 Run람다 식을 취하는 방법이 있으므로 다음을 사용해야합니다.var result = AsyncContext.Run(() => MyAsyncMethod());
Stephen Cleary

1
Nuget에서 라이브러리를 얻었지만 실제로 RunTask방법 이없는 것 같습니다 . 내가 찾을 수있는 가장 가까운 것은 Run이지만 Result속성 이 없습니다 .
Asad Saeeduddin


313

마침내 내 문제를 해결 한 솔루션을 추가하면 누군가의 시간을 절약 할 수 있기를 바랍니다.

먼저 Stephen Cleary 의 두 기사를 읽으십시오 .

"비동기 코드에서 차단하지 말 것"의 "두 가지 모범 사례"에서 첫 번째 방법은 작동하지 않았고 두 번째 방법은 적용 할 수 없었습니다 (기본적으로 사용할 수있는 경우 await).

여기 내 해결 방법이 있습니다. 통화를 감싸고 더 이상 교착 상태가Task.Run<>(async () => await FunctionAsync()); 없도록하십시오 .

내 코드는 다음과 같습니다.

public class LogReader
{
    ILogger _logger;

    public LogReader(ILogger logger)
    {
        _logger = logger;
    }

    public LogEntity GetLog()
    {
        Task<LogEntity> task = Task.Run<LogEntity>(async () => await GetLogAsync());
        return task.Result;
    }

    public async Task<LogEntity> GetLogAsync()
    {
        var result = await _logger.GetAsync();
        // more code here...
        return result as LogEntity;
    }
}

5
2 년이 지난 지금이 ​​솔루션이 어떻게 유지되고 있는지 궁금합니다. 어떤 소식? 초보자에게는이 접근 방식에 미묘한 부분이 있습니까?
Dan Esparza

26
교착 상태는 아니지만 원래 스레드의 동기화 컨텍스트 외부에서 새 스레드에서 실행되어야하기 때문입니다. 그러나 특히 환경이 좋지 않은 환경, 특히 웹 응용 프로그램이 있습니다. 이로 인해 웹 서버에 사용 가능한 스레드가 절반으로 줄어들 수 있습니다 (요청에 대한 스레드 하나와 이에 대한 스레드 하나). 더 많이할수록 더 나빠집니다. 전체 웹 서버를 교착 상태에 빠뜨릴 수 있습니다.
Chris Pratt

30
@ChrisPratt- Task.Run()비동기 코드에서 모범 사례가 아니기 때문에 옳습니다 . 그러나 원래 질문에 대한 답은 무엇입니까? 비동기 메서드를 동 기적으로 호출하지 않습니까? 우리는 원하지만 실제 세계에서는 때때로해야합니다.
Tohid

1
@ Tohid 당신은 Stephen Cleary의 도서관을 시험해 볼 수 있습니다. 나는 사람들이 이것을 가정하고 Parallel.ForEach학대가 '실제 세계'에 영향을 미치지 않을 것이며 결국 서버를 무너 뜨릴 것이라고 보았다 . 이 코드는 콘솔 앱에는 문제가 없지만 @ChrisPratt가 말했듯이 웹 앱에는 사용하지 않아야합니다. "지금"작동하지만 확장 할 수는 없습니다.
makhdumi 1

1
난 그냥이 하나를지지하기에 충분한 포인트를 얻기 위해 질문에 대답 새 계정을 만들기 시작 관심이 ....
Giannis Paraskevopoulos

206

Microsoft는 Async를 Sync로 실행하기 위해 AsyncHelper (내부) 클래스를 구축했습니다. 소스는 다음과 같습니다.

internal static class AsyncHelper
{
    private static readonly TaskFactory _myTaskFactory = new 
      TaskFactory(CancellationToken.None, 
                  TaskCreationOptions.None, 
                  TaskContinuationOptions.None, 
                  TaskScheduler.Default);

    public static TResult RunSync<TResult>(Func<Task<TResult>> func)
    {
        return AsyncHelper._myTaskFactory
          .StartNew<Task<TResult>>(func)
          .Unwrap<TResult>()
          .GetAwaiter()
          .GetResult();
    }

    public static void RunSync(Func<Task> func)
    {
        AsyncHelper._myTaskFactory
          .StartNew<Task>(func)
          .Unwrap()
          .GetAwaiter()
          .GetResult();
    }
}

Microsoft.AspNet.Identity 기본 클래스에는 Async 메서드 만 있으며 Sync로 호출하기 위해 다음과 같은 확장 메서드가있는 클래스가 있습니다 (예제 사용법).

public static TUser FindById<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<TUser>(() => manager.FindByIdAsync(userId));
}

public static bool IsInRole<TUser, TKey>(this UserManager<TUser, TKey> manager, TKey userId, string role) where TUser : class, IUser<TKey> where TKey : IEquatable<TKey>
{
    if (manager == null)
    {
        throw new ArgumentNullException("manager");
    }
    return AsyncHelper.RunSync<bool>(() => manager.IsInRoleAsync(userId, role));
}

라이센스 코드 조항에 관심이있는 사용자를 위해 다음은 Microsoft에서 MIT 라이센스를 받았음을 나타내는 주석이있는 매우 유사한 코드에 대한 링크입니다 (스레드에서 문화권 지원 만 추가). https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs


2
내 비동기 메소드가 다른 비동기 메소드를 기다립니다. 로 await통화를 꾸미지 않습니다 ConfigureAwait(false). Global.asax AsyncHelper.RunSyncApplication_Start()함수에서 비동기 함수를 호출하는 데 사용하려고 시도했지만 작동하는 것 같습니다. 이것이 AsyncHelper.RunSync내가이 게시물의 다른 곳에서 읽은 "발신자 컨텍스트로의 마샬링"교착 상태 문제가 발생하지 않는 것을 의미합니까 ?
Bob.at.Indigo.Health

1
@ Bob.at.SBS는 코드가 수행하는 작업에 따라 다릅니다. 이 코드를 사용하는 것처럼 안전 하지 않은 것은 간단하지 않습니다 . 이것은 비동기 명령을 동기식으로 실행하는 매우 최소한의 반 안전 방법이며 교착 상태를 유발하기 위해 부적절하게 쉽게 사용할 수 있습니다.
Erik Philips

1
감사. 2 개의 후속 질문 : 1) 비동기 메소드가 피하고 싶을 때 교착 상태를 유발할 수있는 예를 제시 할 수 있습니까? 2)이 맥락에서 교착 상태는 종종 타이밍에 의존합니까? 실제로 작동하는 경우 코드에 여전히 타이밍 종속 교착 상태가 발생할 수 있습니까?
Bob.at.Indigo.Health

@ Bob.at.SBS 오른쪽 상단의 질문하기 버튼을 사용하여 질문하는 것이 좋습니다 . 이 질문에 대한 링크를 포함하거나 질문에 답변 할 수 있습니다.
Erik Philips

1
@ Bob.at ... Erik이 제공 한 코드는 Asp에서 완벽하게 작동합니다. 순 mvc5 및 EF6,하지만 난 다른 솔루션 (ConfigureAwait (false)를 .GetAwaiter () getResult를 () 또는 .result.) 중 하나를 시도 할 때 중단 완전히 내 웹 응용 프로그램
LeonardoX

150

async Main은 이제 C # 7.2의 일부이며 프로젝트 고급 빌드 설정에서 활성화 할 수 있습니다.

C # <7.2의 경우 올바른 방법은 다음과 같습니다.

static void Main(string[] args)
{
   MainAsync().GetAwaiter().GetResult();
}


static async Task MainAsync()
{
   /*await stuff here*/
}

예를 들어 https://docs.microsoft.com/en-us/azure/service-bus-messaging/service-bus-dotnet-how-to-use- 주제-구독


11
나는 왜 누군가가 이것을 거부했는지 전혀 모른다. 이것은 나를 위해 잘 작동했습니다. 이 수정 없이는 어디에서나 ASYCH를 전파해야했습니다.
죄수 제로

11
왜 이것보다 낫 MainAsync().Wait()습니까?
분쇄

8
동의한다. 이 모든 것 대신 MainAsync (). Wait ()이 필요합니다.
Hajjat

8
@crush 나는 이것이 어떻게 교착 상태를 피할 수 있는지 설명하고있었습니다. 경우에 따라 UI 또는 asp.net 스레드에서 .Wait ()를 호출하면 교착 상태가 발생합니다. 비동기 교착 상태
David

6
@ClintB : ASP.NET Core에서는이 작업을 수행하지 않아야합니다. 웹 응용 프로그램은 특히 스레드 부족에 취약하며,이를 수행 할 때마다 요청에서 서비스를 제공하는 데 사용되는 스레드를 풀에서 가져옵니다. 데스크톱 / 모바일 애플리케이션은 전통적으로 단일 사용자이기 때문에 덜 문제가되지 않습니다.
Chris Pratt

52
public async Task<string> StartMyTask()
{
    await Foo()
    // code to execute once foo is done
}

static void Main()
{
     var myTask = StartMyTask(); // call your method which will return control once it hits await
     // now you can continue executing code here
     string result = myTask.Result; // wait for the task to complete to continue
     // use result

}

'await'키워드를 "이 장기 실행 태스크를 시작한 다음 제어를 호출 메소드로 리턴"으로 읽습니다. 장기 실행 작업이 완료되면 그 후 코드를 실행합니다. 대기 후 코드는 콜백 메소드였던 코드와 유사합니다. 논리적 흐름이라는 큰 차이점은 중단되지 않으므로 쓰기와 읽기가 훨씬 쉽습니다.


15
Wait예외를 감싸고 교착 상태가 발생할 가능성이 있습니다.
Stephen Cleary

를 사용하지 않고 비동기 메소드를 호출하면 await동기식으로 실행될 것이라고 생각했습니다. 적어도 그것은 나를 위해 일하지 않습니다 (을 호출하지 않고 myTask.Wait). 사실, myTask.RunSynchronously()이미 실행 되었기 때문에 전화를 걸 때 예외가 발생 했습니다!
awe

2
나는이 답변을 좋아한다. 작고 우아한 편집에 대한 좋은 의견. 기여해 주셔서 감사합니다! 나는 아직도 동시성을 배우고, 그래서 모든 :) 도움이
kayleeFrye_onDeck

2
이 답변이 오늘날에도 여전히 작동해야합니까? 방금 MVC Razor 프로젝트에서 시도했지만 앱이 액세스를 중단했습니다 .Result.
사라 코딩

7
@TrueBlueAussie 동기화 컨텍스트 교착 상태입니다. 비동기 코드는 동기화 컨텍스트로 마샬링되지만 Result당시 호출에 의해 차단 되므로 도달하지 않습니다. 그리고 Result결코 끝, 그것은이 기다리고 누군가를 기다리고 있기 때문에 Result종료 기본적으로, : D
Luaan

40

100 % 확신 할 수는 없지만 이 블로그에 설명 된 기술 은 많은 상황에서 작동해야 한다고 생각합니다 .

따라서이 task.GetAwaiter().GetResult()전파 로직을 직접 호출하려는 경우 사용할 수 있습니다 .


6
위 의 Stephen Cleary의 답변 A 에서이 방법을 사용합니다. WaitAndUnwrapException 소스를 참조하십시오 .
orad

호출하는 함수가 void 또는 작업 인 경우 GetResult ()를 사용해야합니까? 결과를 다시 받고 싶지
않다면

예. 그렇지 않으면 작업이 완료 될 때까지 차단되지 않습니다. 또한 대신 GetAwaiter를 호출 () getResult를 ()는 () .Wait 호출 할 수 있습니다.
NStuke

1
그것이 "많은 상황"부분입니다. 교착 상태의 위험이 있는지 여부를 판별하기 위해 전체 스레딩 모델과 다른 스레드가 수행하는 작업에 따라 다릅니다.
NStuke

24

그러나 임시 메시지 펌프 (SynchronizationContext)와 같은 모든 상황에서 작동하는 좋은 솔루션이 있습니다 (거의 설명 참조).

호출 스레드는 예상대로 차단되는 반면, 비동기 함수에서 호출 된 모든 연속 요소는 호출 스레드에서 실행되는 애드혹 SynchronizationContext (메시지 펌프)에 마샬링되므로 교착 상태가 발생하지 않도록합니다.

임시 메시지 펌프 도우미의 코드 :

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.Threading
{
    /// <summary>Provides a pump that supports running asynchronous methods on the current thread.</summary>
    public static class AsyncPump
    {
        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Action asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(true);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function
                syncCtx.OperationStarted();
                asyncMethod();
                syncCtx.OperationCompleted();

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static void Run(Func<Task> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Runs the specified asynchronous method.</summary>
        /// <param name="asyncMethod">The asynchronous method to execute.</param>
        public static T Run<T>(Func<Task<T>> asyncMethod)
        {
            if (asyncMethod == null) throw new ArgumentNullException("asyncMethod");

            var prevCtx = SynchronizationContext.Current;
            try
            {
                // Establish the new context
                var syncCtx = new SingleThreadSynchronizationContext(false);
                SynchronizationContext.SetSynchronizationContext(syncCtx);

                // Invoke the function and alert the context to when it completes
                var t = asyncMethod();
                if (t == null) throw new InvalidOperationException("No task provided.");
                t.ContinueWith(delegate { syncCtx.Complete(); }, TaskScheduler.Default);

                // Pump continuations and propagate any exceptions
                syncCtx.RunOnCurrentThread();
                return t.GetAwaiter().GetResult();
            }
            finally { SynchronizationContext.SetSynchronizationContext(prevCtx); }
        }

        /// <summary>Provides a SynchronizationContext that's single-threaded.</summary>
        private sealed class SingleThreadSynchronizationContext : SynchronizationContext
        {
            /// <summary>The queue of work items.</summary>
            private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> m_queue =
                new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
            /// <summary>The processing thread.</summary>
            private readonly Thread m_thread = Thread.CurrentThread;
            /// <summary>The number of outstanding operations.</summary>
            private int m_operationCount = 0;
            /// <summary>Whether to track operations m_operationCount.</summary>
            private readonly bool m_trackOperations;

            /// <summary>Initializes the context.</summary>
            /// <param name="trackOperations">Whether to track operation count.</param>
            internal SingleThreadSynchronizationContext(bool trackOperations)
            {
                m_trackOperations = trackOperations;
            }

            /// <summary>Dispatches an asynchronous message to the synchronization context.</summary>
            /// <param name="d">The System.Threading.SendOrPostCallback delegate to call.</param>
            /// <param name="state">The object passed to the delegate.</param>
            public override void Post(SendOrPostCallback d, object state)
            {
                if (d == null) throw new ArgumentNullException("d");
                m_queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
            }

            /// <summary>Not supported.</summary>
            public override void Send(SendOrPostCallback d, object state)
            {
                throw new NotSupportedException("Synchronously sending is not supported.");
            }

            /// <summary>Runs an loop to process all queued work items.</summary>
            public void RunOnCurrentThread()
            {
                foreach (var workItem in m_queue.GetConsumingEnumerable())
                    workItem.Key(workItem.Value);
            }

            /// <summary>Notifies the context that no more work will arrive.</summary>
            public void Complete() { m_queue.CompleteAdding(); }

            /// <summary>Invoked when an async operation is started.</summary>
            public override void OperationStarted()
            {
                if (m_trackOperations)
                    Interlocked.Increment(ref m_operationCount);
            }

            /// <summary>Invoked when an async operation is completed.</summary>
            public override void OperationCompleted()
            {
                if (m_trackOperations &&
                    Interlocked.Decrement(ref m_operationCount) == 0)
                    Complete();
            }
        }
    }
}

용법:

AsyncPump.Run(() => FooAsync(...));

비동기 펌프에 대한 자세한 설명은 여기를 참조하십시오 .



HttpContext.Current를 임의로 잃을 수 있으므로 Asp.net 시나리오에서는 작동하지 않습니다.
Josh Mouch

12

더 이상이 질문에주의를 기울이는 사람에게는 ...

살펴보면 Microsoft.VisualStudio.Services.WebApi이라는 클래스가 TaskExtensions있습니다. 이 클래스에는 정적 확장 메서드가 표시됩니다 Task.SyncResult().이 메서드 는 작업이 반환 될 때까지 스레드를 완전히 차단합니다.

내부적으로는 호출 task.GetAwaiter().GetResult()그러나 그것은 어떤에서 작업에 과부하가있어, 매우 간단하다 async돌려주는 방법 Task, Task<T>또는 Task<HttpResponseMessage>... 문법 설탕, 아기가 ... 아빠의 달콤한 이빨을 얻었다.

...GetAwaiter().GetResult()차단 컨텍스트에서 비동기 코드를 실행하는 MS 공식 방법 인 것 같습니다 . 내 유스 케이스에 매우 잘 작동하는 것 같습니다.


3
"완전히 블록처럼"
Dawood ibn Kareem

9
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);

OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();

또는 이것을 사용하십시오 :

var result=result.GetAwaiter().GetResult().AccessToken

6

동기 코드에서 비동기 메소드를 호출 할 수 있습니다. 즉, 필요할 때까지 await표시해야합니다 async.

많은 사람들이 여기에서 제안하는 것처럼 동기 메서드의 결과 작업에서 Wait () 또는 Result를 호출 할 수는 있지만 해당 메서드의 차단 호출로 인해 비동기의 목적을 무효화합니다.

나는 당신이 정말로 메소드를 만들 수없고 async동기 메소드를 잠그고 싶지 않다면 콜백 메소드를 매개 변수로 태스크의 ContinueWith 메소드에 매개 변수로 전달하여 사용해야합니다.


5
그렇다면 그것은 지금 동 기적으로 메소드를 호출하지 않을 것입니까?
Jeff Mercado

2
내가 이해하는 것처럼, 비 비동기 메소드에서 비동기 메소드를 호출 할 수 있다는 것이 문제였습니다. 차단 방식으로 비동기 메소드를 호출 할 필요는 없습니다.
base2

죄송합니다. 귀하의 "그들 async도 역시 표시되어야합니다 "는 귀하가 실제로 말하고있는 것과 다른 관심을 끌었습니다.
Jeff Mercado

비동기에 대해 신경 쓰지 않는다면이 방법으로 호출해도됩니까 (Stephen Cleary가 계속 잔소리하는 랩 예외의 교착 상태 가능성은 어떻습니까?) 테스트 방법이 있습니다 (동기식으로 실행해야 함) 비동기 메소드를 테스트합니다. 계속하기 전에 결과를 기다려야하므로 비동기 메소드의 결과를 테스트 할 수 있습니다.
awe

6

나는 너무 늦었다는 것을 안다. 그러나 나와 같은 누군가가 다른 라이브러리에 의존하지 않고 깔끔하고 쉬운 방법 으로이 문제를 해결하고 싶을 경우.

Ryan 에서 다음 코드 를 찾았습니다.

public static class AsyncHelpers
{
    private static readonly TaskFactory taskFactory = new
        TaskFactory(CancellationToken.None,
            TaskCreationOptions.None,
            TaskContinuationOptions.None,
            TaskScheduler.Default);

    /// <summary>
    /// Executes an async Task method which has a void return value synchronously
    /// USAGE: AsyncUtil.RunSync(() => AsyncMethod());
    /// </summary>
    /// <param name="task">Task method to execute</param>
    public static void RunSync(Func<Task> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();

    /// <summary>
    /// Executes an async Task<T> method which has a T return type synchronously
    /// USAGE: T result = AsyncUtil.RunSync(() => AsyncMethod<T>());
    /// </summary>
    /// <typeparam name="TResult">Return Type</typeparam>
    /// <param name="task">Task<T> method to execute</param>
    /// <returns></returns>
    public static TResult RunSync<TResult>(Func<Task<TResult>> task)
        => taskFactory
            .StartNew(task)
            .Unwrap()
            .GetAwaiter()
            .GetResult();
}

다음과 같이 부를 수 있습니다

var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());

6
정확히 위의 같은이 보이는 대답은 내가 뭔가를 놓친 거지
inlokesh

2

몇 가지 다른 방법을 시도했지만 몇 가지 성공을 거두자 이것이 끝났습니다. 결과를 얻는 동안 교착 상태로 끝나지 않으며 랩 된 예외가 아닌 원래 예외를 가져 와서 던집니다.

private ReturnType RunSync()
{
  var task = Task.Run(async () => await myMethodAsync(agency));
  if (task.IsFaulted && task.Exception != null)
  {
    throw task.Exception;
  }

  return task.Result;
}

반환 작업과 함께 작동합니다 .GetAwaiter (). GetResult ();
G

예,하지만 원래 예외는 어떻습니까?
Jiří Herník

.Result 나는 기본적으로 .GetAwaiter (). GetResult ()와 동일하다고 생각합니다.
G 당

-2

새 스레드에서 호출 할 수 있습니다 (스레드 풀에서는 아닙니다!).

public static class SomeHelperClass
{ 
       public static T Result<T>(Func<T> func)
        {
            return Task.Factory.StartNew<T>(
                  () => func()
                , TaskCreationOptions.LongRunning
                ).Result;
        }
}
...
content = SomeHelperClass.Result<string>(
  () => response.Content.ReadAsStringAsync().Result
  );

-3

이 창 비동기 메소드에는 AsTask ()라는 멋진 작은 메소드가 있습니다. 이를 사용하여 메소드 자체를 태스크로 리턴하여 수동으로 Wait ()를 호출 할 수 있습니다.

예를 들어 Windows Phone 8 Silverlight 응용 프로그램에서 다음을 수행 할 수 있습니다.

private void DeleteSynchronous(string path)
{
    StorageFolder localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
    Task t = localFolder.DeleteAsync(StorageDeleteOption.PermanentDelete).AsTask();
    t.Wait();
}

private void FunctionThatNeedsToBeSynchronous()
{
    // Do some work here
    // ....

    // Delete something in storage synchronously
    DeleteSynchronous("pathGoesHere");

    // Do other work here 
    // .....
}

도움이 되었기를 바랍니다!


-4

당신이 그것을 실행하려면 동기화

MethodAsync().RunSynchronously()

3
이 방법은 콜드 작업을 시작하기위한 것입니다. 일반적으로 비동기 메소드는 핫 태스크, 즉 이미 시작된 태스크를 리턴합니다. RunSynchronously()핫 태스크를 호출 하면 결과가입니다 InvalidOperationException. 이 코드로 시도해보십시오 :Task.Run(() => {}).RunSynchronously();
Theodor Zoulias

-5
   //Example from non UI thread -    
   private void SaveAssetAsDraft()
    {
        SaveAssetDataAsDraft();
    }
    private async Task<bool> SaveAssetDataAsDraft()
    {
       var id = await _assetServiceManager.SavePendingAssetAsDraft();
       return true;   
    }
   //UI Thread - 
   var result = Task.Run(() => SaveAssetDataAsDraft().Result).Result;

2
교착 상태를 생성하십시오. 답을 더 잘 삭제하십시오.
PreguntonCojoneroCabrón

Task.Run (() => SaveAssetDataAsDraft ()). Result; -교착 상태를 생성하지 않음
Anubis
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.