J'ai une méthode async
:
public async Task<string> GenerateCodeAsync()
{
string code = await GenerateCodeService.GenerateCodeAsync();
return code;
}
J'ai besoin d'appeler cette méthode à partir d'une méthode synchrone.
Comment puis-je faire cela sans avoir à dupliquer la méthode GenerateCodeAsync
pour que cela fonctionne de manière synchrone?
Mise à jour
Pourtant, aucune solution raisonnable trouvée.
Cependant, je vois que HttpClient
implémente déjà ce modèle
using (HttpClient client = new HttpClient())
{
// async
HttpResponseMessage responseAsync = await client.GetAsync(url);
// sync
HttpResponseMessage responseSync = client.GetAsync(url).Result;
}
Vous pouvez accéder à la propriété Result
de la tâche, ce qui entraînera le blocage de votre thread jusqu'à ce que le résultat soit disponible:
string code = GenerateCodeAsync().Result;
Remarque: Dans certains cas, cela pourrait entraîner un blocage: votre appel à Result
bloque le thread principal, empêchant ainsi l'exécution du reste du code asynchrone. Vous avez les options suivantes pour vous assurer que cela ne se produit pas:
.ConfigureAwait(false)
à votre méthode de bibliothèque ou exécutez explicitement votre méthode async dans un thread de pool de threads et attendez qu'elle se termine:
string code = Task.Run(GenerateCodeAsync).Result;
Vous devez obtenir l'attendeur (GetAwaiter()
) et mettre fin à l'attente de l'achèvement de la tâche asynchrone (GetResult()
).
string code = GenerateCodeAsync().GetAwaiter().GetResult();
Vous devriez pouvoir faire cela en utilisant des délégués, expression lambda
private void button2_Click(object sender, EventArgs e)
{
label1.Text = "waiting....";
Task<string> sCode = Task.Run(async () =>
{
string msg =await GenerateCodeAsync();
return msg;
});
label1.Text += sCode.Result;
}
private Task<string> GenerateCodeAsync()
{
return Task.Run<string>(() => GenerateCode());
}
private string GenerateCode()
{
Thread.Sleep(2000);
return "I m back" ;
}
J'ai besoin d'appeler cette méthode à partir d'une méthode synchrone.
C'est possible avec GenerateCodeAsync().Result
ou GenerateCodeAsync().Wait()
, comme le suggère l'autre réponse. Cela bloquerait le thread actuel jusqu'à ce que GenerateCodeAsync
soit terminé.
Cependant, votre question est taguée avec asp.net , et vous avez également laissé le commentaire:
J'espérais une solution plus simple, pensant qu'asp.net gérait cela beaucoup plus facilement que d'écrire autant de lignes de code
Mon point est, vous ne devriez pas bloquer sur une méthode asynchrone dans ASP.NET. Cela réduira l'évolutivité de votre application Web et risque de créer un blocage (lorsqu'une await
continuation à l'intérieur de GenerateCodeAsync
est publiée dans AspNetSynchronizationContext
). Utiliser Task.Run(...).Result
pour décharger quelque chose sur un thread de pool, puis le bloquer nuira davantage à l'évolutivité, car il encourt +1 thread supplémentaire pour traiter une requête HTTP donnée.
ASP.NET prend en charge les méthodes asynchrones, soit via des contrôleurs asynchrones (dans ASP.NET MVC et Web API), soit directement via AsyncManager
et PageAsyncTask
dans ASP.NET classique. Vous devriez l'utiliser. Pour plus de détails, consultez cette réponse .
Microsoft Identity a des méthodes d'extension qui appellent des méthodes asynchrones de manière synchrone. Par exemple, il existe une méthode GenerateUserIdentityAsync () et égale à CreateIdentity ()
Si vous regardez UserManagerExtensions.CreateIdentity (), cela ressemble à ceci:
public static ClaimsIdentity CreateIdentity<TUser, TKey>(this UserManager<TUser, TKey> manager, TUser user,
string authenticationType)
where TKey : IEquatable<TKey>
where TUser : class, IUser<TKey>
{
if (manager == null)
{
throw new ArgumentNullException("manager");
}
return AsyncHelper.RunSync(() => manager.CreateIdentityAsync(user, authenticationType));
}
Voyons maintenant ce que fait AsyncHelper.RunSync
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
var cultureUi = CultureInfo.CurrentUICulture;
var culture = CultureInfo.CurrentCulture;
return _myTaskFactory.StartNew(() =>
{
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = cultureUi;
return func();
}).Unwrap().GetAwaiter().GetResult();
}
Donc, ceci est votre wrapper pour la méthode async. Et s'il vous plaît ne lisez pas les données de Result - cela bloquera potentiellement votre code dans ASP.
Il y a un autre moyen - ce qui est suspect pour moi, mais vous pouvez aussi l'envisager
Result r = null;
YourAsyncMethod()
.ContinueWith(t =>
{
r = t.Result;
})
.Wait();
Pour éviter les blocages, j'essaie toujours d'utiliser Task.Run()
lorsque je dois appeler une méthode asynchrone de manière synchrone que @Heinzi mentionne.
Cependant, la méthode doit être modifiée si la méthode asynchrone utilise des paramètres. Par exemple, Task.Run(GenerateCodeAsync("test")).Result
donne l'erreur:
Argument 1: impossible de convertir de '
System.Threading.Tasks.Task<string>
' en 'System.Action'
Cela pourrait s'appeler comme ceci à la place:
string code = Task.Run(() => GenerateCodeAsync("test")).Result;
L'autre façon pourrait être si vous voulez attendre que la tâche soit terminée:
var t = GenerateCodeService.GenerateCodeAsync();
Task.WhenAll(t);
string code = t.Result;