Как вызвать асинхронный метод из синхронного метода в C#?
-
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:
- Синхронный метод вызывает асинхронный метод, получая
Task
. - Синхронный метод делает блокирование
Task
. - А
async
Метод используетawait
безConfigureAwait
. - А
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;