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

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

Возможно ли это?

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

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

Ответ 1

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

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

Решение А

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

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

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

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

Решение B

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

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

* Обновление от 14.04.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 в максимально возможной степени.

Решение С

AsyncContext.RunTask не будет работать в каждом сценарии. Например, если метод async ожидает чего-то, для чего требуется событие пользовательского интерфейса, то вы будете в тупике даже с вложенным контекстом. В этом случае вы можете запустить метод async в пуле потоков:

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

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

Обновление, 2019-05-01: Текущие "наихудшие практики" описаны в статье MSDN здесь.

Ответ 2

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

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

Из "двух лучших практик" в "Do not Block on Async Code" первый из них не работал у меня, а второй не применим (в основном, если я могу использовать await, я делаю!).

Итак, вот мой обход: заверните вызов внутри Task.Run<>(async () => await FunctionAsync()); и, надеюсь, no deadlock.

Вот мой код:

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

Ответ 3

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

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

Ответ 4

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

Для С# <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- Тем-подписка

Ответ 5

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

}

Вы читаете ключевое слово "ожидание" как "запускаете эту задачу, выполняющую длинные действия, а затем возвращаете управление вызывающему методу". После выполнения длительной задачи он выполняет код после него. Код после ожидания похож на то, что раньше было методом CallBack. Большая разница в логическом потоке не прерывается, что значительно облегчает запись и чтение.

Ответ 6

Я не уверен на 100%, но я считаю, что техника, описанная в этом блоге, должна работать во многих случаях:

Таким образом, вы можете использовать task.GetAwaiter().GetResult(), если вы хотите напрямую вызвать эту логику распространения.

Ответ 7

Наиболее приемлемый ответ не совсем корректен. Существует решение, которое работает в любой ситуации: специальный поток сообщений (SynchronizationContext).

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

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

Ответ 8

Всем, кто больше обращает внимание на этот вопрос...

Если вы посмотрите в Microsoft.VisualStudio.Services.WebApi существует класс с именем TaskExtensions. Внутри этого класса вы увидите статический метод расширения Task.SyncResult(), который как бы полностью блокирует поток до тех пор, пока задача не вернется.

Внутренне он вызывает task.GetAwaiter().GetResult() что довольно просто, однако он перегружен для работы с любым async методом, который возвращает Task, Task<T> или Task<HttpResponseMessage>... синтаксический sugar, baby... daddy сладкоежка

Похоже ...GetAwaiter().GetResult() - официальный MS-способ выполнения асинхронного кода в контексте блокировки. Кажется, работает очень хорошо для моего случая использования.

Ответ 9

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

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

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

Ответ 10

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

Я нашел следующий кусок кода от Райана

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

Ответ 11

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

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

Или используйте это:

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

Ответ 12

Если у вас есть асинхронный метод RefreshList, вы можете вызвать этот асинхронный метод из неасинхронного метода, как показано ниже.

Task.Run(async () => { await RefreshList(); });

Ответ 13

Вот очень простой пример вызова метода async из синхронного:

static void Main()
{
    var output = GetWebContent();
    Console.WriteLine(output.Result);
    Console.ReadLine();
}

static async Task<string> GetWebContent()
{
    string url = "http://google.com";
    string content = await new WebClient().DownloadStringTaskAsync(url);
    return content;
}

Ответ 14

Если вы хотите запустить его Sync

MethodAsync().RunSynchronously()

Ответ 15

Эти методы async для 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 
    // .....
}

Надеюсь, это поможет!

Ответ 16

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

Ответ 17

Прежде всего вам нужно изменить public async void Foo() на public async Task Foo(). async void должен использоваться только в обработчиках событий.

Пример 1:

public async Task Foo(){...}    
var task = Foo();
task.RunSynchronously();

Пример 2:

public async Task<int> Bar(){...}
var task = Bar();
task.RunSynchronously();
var result = task.Result;

Ответ 18

Используйте метод Task.Run < > для этого.

Также, как я выполняю все запросы LINQ и операции Entity Framework, которые не имеют методов -Async.

Если вы используете MVVM, проверьте эти видео в async и ожидайте.