Comment appeler la méthode asynchrone de méthode synchrone en C #?
-
27-10-2019 - |
Question
J'ai une méthode public async void Foo()
que je veux appeler de méthode synchrone. Jusqu'à présent, tout ce que je l'ai vu de la documentation MSDN appelle les méthodes asynchrones via des méthodes async, mais mon programme complet ne se construit pas avec des méthodes async.
Est-ce même possible?
Voici un exemple d'appeler ces méthodes à partir d'une méthode asynchrone: http://msdn.microsoft.com/en-us/library/hh300224 (v = vs.110) .aspx
Maintenant, je suis à la recherche en appelant ces méthodes asynchrones de méthodes de synchronisation.
La solution
Programmation asynchrone ne « croître » à travers la base de code. Il a été href="https://blogs.msdn.microsoft.com/lucian/2011/04/15/async-ctp-refresh-design-changes/" par rapport à un virus zombie . La meilleure solution est de lui permettre de grandir, mais parfois ce n'est pas possible.
Je l'ai écrit quelques types dans ma bibliothèque Nito.AsyncEx pour traiter un code partiellement asynchrone base. Il n'y a pas solution qui fonctionne dans toutes les situations, cependant.
Solution A
Si vous avez une méthode simple asynchrone qui n'a pas besoin de synchroniser à son contexte, vous pouvez utiliser Task.WaitAndUnwrapException
:
var task = MyAsyncMethod();
var result = task.WaitAndUnwrapException();
Vous faites pas veulent utiliser Task.Wait
ou Task.Result
parce qu'ils enveloppent exceptions dans AggregateException
.
Cette solution est appropriée que si MyAsyncMethod
ne synchronise pas à son contexte. En d'autres termes, tous les await
en MyAsyncMethod
doit se terminer par ConfigureAwait(false)
. Cela signifie qu'il ne peut pas mettre à jour les éléments d'interface utilisateur ou l'accès au contexte de la requête ASP.NET.
Solution B
Si MyAsyncMethod
n'a pas besoin de synchroniser à 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 les plus récentes de la bibliothèque de l'API est comme suit:
var result = AsyncContext.Run(MyAsyncMethod);
(Il va bien à l'utilisation Task.Result
dans cet exemple parce que RunTask
propagera exceptions Task
).
La raison pour laquelle vous devrez peut-être AsyncContext.RunTask
au lieu de Task.WaitAndUnwrapException
est à cause d'une possibilité de blocage plutôt subtile qui se passe sur WinForms / WPF / SL / ASP.NET:
- Procédé synchrone appelle une méthode asynchrone, l'obtention d'un
Task
. - La méthode synchrone fait un blocage d'attente sur le
Task
. - La méthode de
async
utilisationsawait
sansConfigureAwait
. - Le
Task
ne peut pas terminer dans cette situation parce qu'elle complète que lorsque la méthode deasync
est fini; la méthodeasync
ne peut pas complète parce qu'il tente de planifier sa continuation auSynchronizationContext
et WinForms / WPF / SL / ASP.NET ne permettra pas la poursuite de fonctionner parce que la méthode synchrone est en cours d'exécution dans ce contexte.
Ceci est une raison pour laquelle il est une bonne idée d'utiliser ConfigureAwait(false)
au sein de chaque méthode async
autant que possible.
Solution C
AsyncContext.RunTask
ne fonctionne pas dans tous les scénarios. Par exemple, si la méthode async
attend quelque chose qui a besoin d'un événement d'interface utilisateur pour complète, alors vous IMPASSE même avec le contexte imbriqué. Dans ce cas, vous pouvez commencer la méthode async
sur le pool de threads:
var task = Task.Run(async () => await MyAsyncMethod());
var result = task.WaitAndUnwrapException();
Cependant, cette solution nécessite une MyAsyncMethod
qui fonctionnera dans le cadre du pool de threads. Donc, il ne peut pas mettre à jour l'interface utilisateur ou éléments accéder au contexte de demande ASP.NET. Et dans ce cas, vous pouvez ainsi ajouter ConfigureAwait(false)
à ses déclarations await
et solution d'utilisation A.
Mise à jour, 01/05/2019: Le courant "moins pires pratiques" sont dans une article MSDN .
Autres conseils
Ajout d'une solution qui a finalement résolu mon problème, je l'espère gagner du temps de quelqu'un.
lire tout d'abord quelques articles de Stephen Cleary:
A partir des « deux meilleures pratiques » dans « Ne pas obstruer le code Async », le premier n'a pas fonctionné pour moi et le second était pas applicable (essentiellement si je peux utiliser await
, je fais!) .
Alors, voici ma solution de contournement. Envelopper l'appel dans un Task.Run<>(async () => await FunctionAsync());
et je l'espère pas impasse 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;
}
}
Microsoft a construit une AsyncHelper classe (interne) pour exécuter Async comme Sync. Les regards source comme:
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();
}
}
Les classes de base de Microsoft.AspNet.Identity n'ont des méthodes Async et afin de les appeler comme Sync il existe 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 les personnes concernées sur les conditions de licence de code, voici un lien vers un code très similaire (ajoute tout soutien à la culture sur le fil) qui a des commentaires pour indiquer qu'il est MIT sous licence par Microsoft. https://github.com/aspnet /AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
async principal fait maintenant partie de C # 7.2 et peut être activée dans les projets de construction avancés paramètres.
Pour C # <7.2, la manière correcte est:
static void Main(string[] args)
{
MainAsync().GetAwaiter().GetResult();
}
static async Task MainAsync()
{
/*await stuff here*/
}
Vous verrez cela utilisé dans beaucoup de documentation Microsoft, par exemple: 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
}
Vous avez lu le « await » mot-clé comme « commencer cette tâche longue course, puis revenir le contrôle à la méthode d'appel ». Une fois la tâche longue est fait, il exécute le code après. Le code après l'await est similaire à ce qui était autrefois les méthodes callback. La grande différence étant le flux logique ne soit pas interrompu ce qui le rend beaucoup plus facile à écrire et à lire.
Je ne suis pas sûr à 100%, mais je crois que la technique décrite dans ce blog devrait fonctionner dans de nombreuses circonstances:
Vous pouvez ainsi utiliser
task.GetAwaiter().GetResult()
si vous voulez appeler directement cette logique de propagation.
La réponse la plus acceptée est pas tout à fait correct. Il y a une solution qui fonctionne dans toutes les situations. Une pompe de message ad hoc (SynchronizationContext)
Le thread appelant sera bloqué comme prévu, tout en veillant à ce que tous les continuations appelés de la fonction async n'IMPASSE pas comme ils vont être mises à profit pour la (pompe de messages) ad-hoc SynchronizationContext en cours d'exécution sur le thread appelant.
Le code de l'aide de la pompe de message ad hoc:
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 .
Pour toute personne de prêter attention à cette question plus ...
Si vous regardez dans Microsoft.VisualStudio.Services.WebApi
il y a une classe appelée TaskExtensions
. Au sein de cette classe, vous verrez la méthode d'extension statique Task.SyncResult()
, qui, comme TOTALLEMENT bloque le fil jusqu'à ce que le retour de la tâche.
En interne, il appelle task.GetAwaiter().GetResult()
ce qui est assez simple, mais il est surchargé de travail sur une méthode de async
que Task
de retour, Task<T>
ou Task<HttpResponseMessage>
... sucre syntaxique, bébé ... de papa obtenu une dent sucrée.
On dirait que ...GetAwaiter().GetResult()
est la façon dont les MS-officiels pour exécuter du code async dans un contexte de blocage. Il semble fonctionner très bien pour mon cas d'utilisation.
var result = Task.Run(async () => await configManager.GetConfigurationAsync()).ConfigureAwait(false);
OpenIdConnectConfiguration config = result.GetAwaiter().GetResult();
Ou utilisez ceci:
var result=result.GetAwaiter().GetResult().AccessToken
Vous pouvez appeler une méthode asynchrone à partir du code synchrone, qui est, jusqu'à ce que vous devez await
sur eux, auquel cas ils doivent être marqués async
aussi.
Comme beaucoup de gens suggère ici, vous pouvez appeler Wait () ou le résultat sur la tâche résultant dans votre méthode synchrone, mais vous vous retrouvez avec un appel de blocage dans cette méthode, qui sorte de défaites dans le but de async .
Je vous ne pouvez vraiment pas faire votre méthode async
et vous ne voulez pas bloquer la méthode synchrone, alors vous allez devoir utiliser une méthode de rappel en passant comme paramètre à la méthode ContinueWith la tâche.
Je sais que je suis si tard. Mais au cas où quelqu'un comme moi voulait résoudre ce dans un cadre soigné, facile et sans dépendre d'une autre bibliothèque.
J'ai trouvé le morceau de code de Ryan
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();
}
vous pouvez l'appeler comme ça
var t = AsyncUtil.RunSync<T>(() => AsyncMethod<T>());
Si vous voulez exécuter Sync
MethodAsync().RunSynchronously()
Les fenêtres ont un async méthodes peu nifty méthode appelée AsTask (). Vous pouvez l'utiliser pour avoir le retour de la méthode elle-même comme une tâche de sorte que vous pouvez appeler manuellement Wait () sur elle.
Par exemple, sur une application Silverlight Windows Phone 8, 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
// .....
}
Hope this helps!
//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;