Comment appeler la méthode asynchrone de la méthode synchrone en C#?
j'ai une méthode public async void Foo()
que je veux appeler de méthode synchrone. Jusqu'à présent, tout ce que j'ai vu à partir de la documentation MSDN est d'appeler les méthodes async via les méthodes async, mais tout mon programme n'est pas construit avec les méthodes async.
est-ce possible?
voici un exemple d'appel de ces méthodes à partir d'une méthode asynchrone: http://msdn.microsoft.com/en-us/library/hh300224 (v=V110).aspx
maintenant je cherche à appeler ces méthodes async des méthodes de synchronisation.
11 réponses
la programmation asynchrone "croît" à travers la base de code. Il a été comparé à un virus zombie . La meilleure solution est de le laisser pousser, mais parfois ce n'est pas possible.
j'ai écrit quelques types dans mon Nito.Asyncex bibliothèque pour traiter avec une base de code partiellement asynchrone. Il n'y a pas de solution qui fonctionne dans toutes les situations, cependant.
Une Solution
si vous avez une méthode asynchrone simple qui n'a pas besoin de synchroniser retour à son contexte, alors vous pouvez utiliser Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
Vous ne pas à utiliser Task.Wait
ou Task.Result
parce qu'elles enveloppent des exceptions dans AggregateException
.
Cette solution n'est appropriée que si MyAsyncMethod
ne se synchronise pas avec son contexte. En d'autres termes, tous les await
dans MyAsyncMethod
doit se terminer par ConfigureAwait(false)
. Cela signifie qu'il ne peut pas mettre à jour les éléments de L'interface utilisateur ou accéder au ASP.NET contexte de la demande.
Solution B
si MyAsyncMethod
doit se synchroniser à nouveau avec son contexte, alors vous pouvez être en mesure d'utiliser AsyncContext.RunTask
pour fournir un contexte imbriqué:
var result = AsyncContext.RunTask(MyAsyncMethod).Result;
*mise à jour 14/04/2014: dans les versions plus récentes du bibliothèque L'API est comme suit:
var result = AsyncContext.Run(MyAsyncMethod);
(il est possible d'utiliser Task.Result
dans cet exemple car RunTask
propagera Task
exceptions).
la raison pour laquelle vous pourriez avoir besoin de AsyncContext.RunTask
au lieu de Task.WaitAndUnwrapException
est à cause d'une possibilité d'impasse plutôt subtile qui se produit sur WinForms/WPF/SL/ASP.NET:
- Une méthode synchrone appelle une méthode asynchrone, l'obtention d'un
Task
. - la méthode synchrone fait une attente de blocage sur le
Task
. - la "méthode 1519210920" utilise
await
sansConfigureAwait
. - le
Task
ne peut pas être rempli dans cette situation parce qu'il ne l'est que lorsque la méthodeasync
est terminée; la méthodeasync
ne peut pas être remplie parce qu'elle tente d'inscrire sa continuation auSynchronizationContext
, et WinForms/WPF/SL / ASP.NET ne permettra pas à la suite de s'exécuter parce que la méthode synchrone est déjà en cours d'exécution dans ce contexte.
C'est une des raisons pour lesquelles c'est une bonne idée d'utiliser ConfigureAwait(false)
dans chaque méthode async
autant que possible.
Solution C
AsyncContext.RunTask
ne marchera pas dans tous les scénarios. Par exemple, si la méthode async
nécessite un événement D'interface utilisateur pour terminer, alors vous serez dans l'impasse, même avec le contexte imbriqué. Dans ce cas, vous pouvez lancer la méthode async
sur le pool de threads:
var task = TaskEx.RunEx(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
cependant, cette solution nécessite un MyAsyncMethod
qui fonctionnera dans le contexte du pool de threads. Donc il ne peut pas mettre à jour les éléments de L'interface utilisateur ou accéder au ASP.NET contexte de la demande. Et dans ce cas, vous pouvez aussi bien ajouter ConfigureAwait(false)
à ses déclarations await
, et utiliser la solution A.
Microsoft a construit une classe AsyncHelper (interne) pour exécuter Async comme synchronisation. La source ressemble à:
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();
}
}
The Microsoft.AspNet.Les classes de base d'identité ont seulement des méthodes Async et pour les appeler Sync il y a des classes avec des méthodes d'extension qui ressemblent à (exemple d'utilisation):
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));
}
pour ceux qui sont préoccupés par les conditions de licence du code, Voici un lien vers le code très similaire (vient d'ajouter le soutien à la culture sur la thread) qui a des commentaires pour indiquer qu'il est sous Licence MIT par Microsoft. https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
ajouter une solution qui a finalement résolu mon problème, avec un peu de chance sauve le temps de quelqu'un.
tout D'abord lu un couple d'articles de Stephen Cleary :
tiré de" two best practices "dans" Don't Block on Async Code", le premier n'a pas fonctionné pour moi et le second n'était pas applicable (en gros si je peux utiliser await
, je le fais!).
donc voici mon contournement: envelopper l'appel à l'intérieur d'un Task.Run<>(async () => await FunctionAsync());
et si tout va bien pas deadlock plus.
Voici mon code:
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 fait maintenant partie de C# 7.2 et peut être activé dans les paramètres de construction avancés des projets.
pour C# < 7.2, la bonne façon est:
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
}
vous lisez le mot-clé"attendre" comme "commencer cette tâche longue exécution, puis retourner le contrôle à la méthode d'appel". Une fois la tâche longue terminée, il exécute le code après lui. Le code après l'attente est similaire à ce qui était des méthodes de CallBack. La grande différence étant que le flux logique n'est pas interrompu ce qui le rend beaucoup plus facile à écrire et à lire.
la réponse la plus acceptée n'est pas entièrement correcte. Il y a une solution qui fonctionne dans toutes les situations: une pompe à message ad hoc (SynchronizationContext).
le thread appelant sera bloqué comme prévu, tout en s'assurant que toutes les continuations appelées à partir de la fonction async ne sont pas bloquées car elles seront placées dans le contexte de synchronisation ad-hoc (pompe à message) tournant sur le thread appelant.
le code de la pompe à message ad hoc helper:
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();
}
}
}
}
Utilisation:
AsyncPump.Run(() => FooAsync(...));
une description plus détaillée de la pompe async est disponible ici .
vous pouvez appeler n'importe quelle méthode asynchrone à partir du code synchrone, c'est-à-dire jusqu'à ce que vous ayez besoin de await
sur eux, auquel cas ils doivent être marqués async
aussi.
comme beaucoup de gens le suggèrent ici, vous pourriez appeler Wait() ou le résultat sur la tâche résultante dans votre méthode synchrone, mais alors vous finissez avec un appel de blocage dans cette méthode, qui sorte de défait le but de l'async.
je vous ne pouvez pas vraiment faire de votre méthode async
et vous ne voulez pas verrouiller la méthode synchrone, alors vous allez devoir utiliser une méthode de rappel en la passant comme paramètre à la méthode ContinueWith sur la tâche.
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);
OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();
ou utilisez ceci:
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;
ces méthodes async de windows ont une petite méthode appelée AsTask (). Vous pouvez utiliser ceci pour que la méthode retourne elle-même comme une tâche de sorte que vous puissiez appeler manuellement Wait() dessus.
par exemple, sur une application Windows Phone 8 Silverlight, vous pouvez faire ce qui suit:
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
// .....
}
Espérons que cette aide!