Как вызвать асинхронный метод из синхронного метода в C#?

У меня есть public async void Foo() метод, который я хочу вызвать из синхронного метода. До сих пор все, что я видел из документации MSDN, вызывает асинхронные методы через асинхронные методы, но вся моя программа не построена с асинхронными методами.

это вообще возможно?

вот один пример вызова этих методов из асинхронного метода:http://msdn.microsoft.com/en-us/library/hh300224 (v=против 110).aspx

теперь я смотрю на вызов этих асинхронных методы из методов синхронизации.

11 ответов


асинхронное программирование "растет" через базу кода. Это было по сравнению с зомби-вирус. Лучшее решение-позволить ему расти, но иногда это невозможно.

я написал несколько типов в моем Нито.AsyncEx библиотека для работы с частично асинхронной базой кода. Нет решения, которое работает в любой ситуации, хотя.

Решение A

если у вас есть простой асинхронный метод, который не нужно синхронизировать обратно в его контекст, то вы можете использовать Task.WaitAndUnwrapException:

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

ты не хотите использовать Task.Wait или Task.Result потому что они обертывают исключения в AggregateException.

это решение подходит только в том случае, если MyAsyncMethod не синхронизируется обратно в контекст. Другими словами, каждый await на MyAsyncMethod должно заканчиваться ConfigureAwait(false). Это означает, что он не может обновить какие-либо элементы UI или получить доступ к ASP.NET запрос контекст.

Решение B

если MyAsyncMethod необходимо синхронизировать обратно в контекст, тогда вы можете использовать AsyncContext.RunTask чтобы обеспечить вложенный контекст:

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

*обновление 4/14/2014: в более поздних версиях библиотеки API выглядит следующим образом:

var result = AsyncContext.Run(MyAsyncMethod);

(это нормально использовать Task.Result в этом примере, потому что RunTask для передачи Task исключения).

причина может нужно 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 метод ждет чего-то, что требует завершения события пользовательского интерфейса, тогда вы будете взаимоблокировки даже с вложенным контекстом. В этом случае вы можете начать async метод в пуле потоков:

var task = TaskEx.RunEx(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();

однако для этого решения требуется MyAsyncMethod это будет работать в контексте пула потоков. Поэтому он не может обновить элементы UI или получить доступ к ASP.NET контекст запроса. И в этом случае вы можете добавить ConfigureAwait(false) в своем await заявления, и использовать решение А.


Microsoft построила asynchelper (внутренний) класс для запуска Async как Sync. Источник выглядит так:

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.сеть САШ.Базовые классы Identity имеют только асинхронные методы, и для их вызова как 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));
}

для тех, кто обеспокоен условиями лицензирования кода, вот ссылка на очень похожий код (просто добавляет поддержку культуры в потоке), который имеет комментарии, чтобы указать что это MIT, лицензированный Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs


добавление решения, которое, наконец, решило мою проблему, надеюсь, сэкономит чье-то время.

сначала прочитайте пару статей Стивен Клири:

из "двух лучших практик" в "не блокируйте асинхронный код", первый не работал для меня, а второй не был применим (в основном, если я могу использовать await, I делай!).

Итак, вот мой обходной путь: оберните вызов внутри 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;
    }
}

async Main теперь является частью C# 7.2 и может быть включена в расширенных настройках сборки проектов.

для C#

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


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

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

более подробное описание асинхронного насоса доступно здесь.


вы можете вызвать любой асинхронный метод из синхронного кода, то есть, пока вам не нужно await на них, в этом случае они должны быть отмечены async тоже.

как многие люди предлагают здесь, вы можете вызвать Wait () или Result на результирующую задачу в вашем синхронном методе, но тогда вы в конечном итоге с блокирующим вызовом в этом методе, который своего рода побеждает цель асинхронности.

Я действительно не могу сделать ваш метод async и вы не хотите запирать синхронный метод, затем вам придется использовать метод обратного вызова, передав его в качестве параметра методу ContinueWith в задаче.


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

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

или так:

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

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

эти асинхронные методы windows имеют отличный маленький метод, называемый 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 
    // .....
}

надеюсь, что это помогает!