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

StackOverflow https://stackoverflow.com/questions/9343594

  •  27-10-2019
  •  | 
  •  

Вопрос

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

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

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

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

Это было полезно?

Решение

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

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

Решение а

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

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

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

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

Решение б

Если 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 метод как можно больше.

Решение c

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 здесь.

Другие советы

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

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

Из «двух лучших практик» в «Не блокировать асинхронный код», первый не работал для меня, а второй не был применимы (в основном, если я могу использовать 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 в качестве синхронизации. Источник выглядит как:

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 имеют только асинхровые методы, и для того, чтобы назвать их синхронизацией, есть классы с методами расширения, которые выглядят как (пример использования):

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

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

Для 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-topics-subscriptions

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

}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

MethodAsync().RunSynchronously()

Эти методы Async Async имеют отличный метод, называемый 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 
    // .....
}

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

   //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;
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top