Programing

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

lottogame 2020. 9. 30. 08:38
반응형

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컨텍스트와 다시 동기화되지 않는 경우에만 적합합니다 . 즉, 모든 awaitin MyAsyncMethodConfigureAwait(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. 동기 메서드는 비동기 메서드를 호출하여 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 문서에 있습니다 .


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

먼저 Stephen Cleary 의 몇 가지 기사를 읽으십시오 .

"Do n't Block on Async Code"의 "두 가지 모범 사례"에서 첫 번째는 저에게 적합하지 않았고 두 번째는 적용되지 않았습니다 (기본적으로를 사용할 수 있다면 사용합니다 await!).

그래서 여기 내 해결 방법이 있습니다. a 내부에서 호출을 래핑하고 더 이상 교착 상태가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*/
}

많은 Microsoft 문서에서 사용되는 것을 볼 수 있습니다. 예 : 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'키워드를 "이 장기 실행 작업을 시작한 다음 호출 메서드로 제어를 반환합니다"로 읽습니다. 장기 실행 작업이 완료되면 그 후에 코드를 실행합니다. await 이후의 코드는 CallBack 메서드였던 것과 비슷합니다. 큰 차이점은 논리적 흐름이 중단되지 않아 쓰기 및 읽기가 훨씬 쉽다는 것입니다.


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(...));

비동기 펌프에 대한 자세한 설명은 여기에서 확인할 수 있습니다 .


이 질문에 더 이상 관심을 기울이는 사람에게 ...

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

Internally it calls task.GetAwaiter().GetResult() which is pretty simple, however it's overloaded to work on any async method that return Task, Task<T> or Task<HttpResponseMessage>... syntactic sugar, baby... daddy's got a sweet tooth.

It looks like ...GetAwaiter().GetResult() is the MS-official way to execute async code in a blocking context. Seems to work very fine for my use case.


You can call any asynchronous method from synchronous code, that is, until you need to await on them, in which case they have to be marked async too.

As a lot of people are suggesting here, you could call Wait() or Result on the resulting task in your synchronous method, but then you end up with a blocking call in that method, which sort of defeats the purpose of async.

I you really can't make your method async and you don't want to lock up the synchronous method, then you're going to have to use a callback method by passing it as parameter to the ContinueWith method on task.


I know I'm so late. But in case someone like me wanted to solve this in a neat, easy way, and without depending on another library.

I found the following piece of code from 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();
}

then you can call it like this

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

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

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

Or use this:

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

If you want to run it Sync

MethodAsync().RunSynchronously()

Those windows async methods have a nifty little method called AsTask(). You can use this to have the method return itself as a task so that you can manually call Wait() on it.

For example, on a Windows Phone 8 Silverlight application, you can do the following:

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 
    // .....
}

Hope this helps!


   //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;

참고URL : https://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c

반응형