Как вызвать асинхронный метод из синхронного метода в 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:
- синхронный метод вызывает асинхронный метод, получение
Task
. - синхронный метод делает блокировку ожидания на
Task
. - на
async
способ использованияawait
безConfigureAwait
. - на
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
// .....
}
надеюсь, что это помогает!