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

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

이것도 가능합니까?

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

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



답변

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

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에 있습니다 .


답변

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

먼저 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;
    }
}


답변

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


답변

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- 주제-구독


답변

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


답변

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

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


답변

그러나 임시 메시지 펌프 (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(...));

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