J'ai une application .Net 4.5 multiniveau appelant une méthode utilisant le nouveau C # async
await
mots-clés de et qui se bloque juste et je ne vois pas pourquoi.
En bas, j'ai une méthode asynchrone qui étend notre utilitaire de base de données OurDBConn
(essentiellement un wrapper pour le sous DBConnection
- jacent et les DBCommand
objets):
public static async Task<T> ExecuteAsync<T>(this OurDBConn dataSource, Func<OurDBConn, T> function)
{
string connectionString = dataSource.ConnectionString;
// Start the SQL and pass back to the caller until finished
T result = await Task.Run(
() =>
{
// Copy the SQL connection so that we don't get two commands running at the same time on the same open connection
using (var ds = new OurDBConn(connectionString))
{
return function(ds);
}
});
return result;
}
Ensuite, j'ai une méthode asynchrone de niveau intermédiaire qui appelle ceci pour obtenir des totaux lents:
public static async Task<ResultClass> GetTotalAsync( ... )
{
var result = await this.DBConnection.ExecuteAsync<ResultClass>(
ds => ds.Execute("select slow running data into result"));
return result;
}
Enfin, j'ai une méthode d'interface utilisateur (une action MVC) qui s'exécute de manière synchrone:
Task<ResultClass> asyncTask = midLevelClass.GetTotalAsync(...);
// do other stuff that takes a few seconds
ResultClass slowTotal = asyncTask.Result;
Le problème est qu'il reste à jamais sur cette dernière ligne. Ça fait la même chose si j'appelle asyncTask.Wait()
. Si j'exécute directement la méthode SQL lente, cela prend environ 4 secondes.
Le comportement auquel je m'attends est que lorsque cela arrive asyncTask.Result
, si ce n'est pas fini, il doit attendre jusqu'à ce que ce soit le cas, et une fois qu'il est, il doit renvoyer le résultat.
Si je passe avec un débogueur, l'instruction SQL se termine et la fonction lambda se termine, mais le return result;
ligne de GetTotalAsync
n'est jamais atteinte.
Une idée de ce que je fais mal?
Avez-vous des suggestions sur les domaines où je dois enquêter pour résoudre ce problème?
Cela pourrait-il être une impasse quelque part, et si oui, y a-t-il un moyen direct de le trouver?
SynchronizationContext
.async
-await
clés / .C'est le
async
scénario classique de blocage mixte, comme je le décris sur mon blog . Jason l'a bien décrit: par défaut, un "contexte" est sauvegardé à chaque foisawait
et utilisé pour continuer laasync
méthode. Ce «contexte» est le courant àSynchronizationContext
moins qu'il ne le soitnull
, auquel cas c'est le courantTaskScheduler
. Lorsque laasync
méthode tente de continuer, elle rentre d'abord le «contexte» capturé (dans ce cas, un ASP.NETSynchronizationContext
). ASP.NETSynchronizationContext
n'autorise qu'un seul thread dans le contexte à la fois, et il existe déjà un thread dans le contexte - le thread est bloqué surTask.Result
.Il existe deux directives pour éviter cette impasse:
async
tout en bas. Vous dites que vous «ne pouvez» pas faire cela, mais je ne sais pas pourquoi. ASP.NET MVC sur .NET 4.5 peut certainement prendre en charge desasync
actions, et ce n'est pas une modification difficile à apporter.ConfigureAwait(continueOnCapturedContext: false)
autant que possible. Cela remplace le comportement par défaut de la reprise sur le contexte capturé.la source
ConfigureAwait(false)
que la fonction actuelle reprend dans un contexte différent?async
action sans casser la façon dont cela fonctionne côté client. Je prévois certainement d'étudier cette option à plus long terme.ConfigureAwait(false)
l'arbre d'appel aurait résolu le problème de l'OP.async
n'affecte pas du tout le côté client. J'explique cela dans un autre article de blog,async
Ne change pas le protocole HTTP .async
de "grandir" à travers la base de code. Si votre méthode de contrôleur peut dépendre d'opérations asynchrones, la méthode de classe de base doit retournerTask<ActionResult>
. La transition d'un gros projet versasync
est toujours délicate car le mélangeasync
et la synchronisation du code sont difficiles et délicats. Leasync
code pur est beaucoup plus simple.J'étais dans la même situation de blocage, mais dans mon cas, en appelant une méthode async à partir d'une méthode de synchronisation, ce qui fonctionne pour moi était:
est-ce une bonne approche, une idée?
la source
Juste pour ajouter à la réponse acceptée (pas assez de représentant pour commenter), j'ai eu ce problème lors du blocage de l'utilisation de
task.Result
, événement bien que tous les élémentsawait
ci-dessousConfigureAwait(false)
, comme dans cet exemple:Le problème résidait en fait avec le code de la bibliothèque externe. La méthode de bibliothèque async a essayé de continuer dans le contexte de synchronisation d'appel, quelle que soit la façon dont j'ai configuré l'attente, ce qui a entraîné un blocage.
Ainsi, la réponse était de rouler ma propre version du code de la bibliothèque externe
ExternalLibraryStringAsync
, de sorte qu'elle ait les propriétés de continuation souhaitées.mauvaise réponse à des fins historiques
Après beaucoup de douleur et d'angoisse, j'ai trouvé la solution enfouie dans ce billet de blog (Ctrl-f pour «blocage»). Il tourne autour de l'utilisation
task.ContinueWith
, au lieu du simpletask.Result
.Exemple de blocage précédent:
Évitez l'impasse comme ceci:
la source
Task
ne soit terminé et ne fournissez à l'appelant aucun moyen de déterminer quand la mutation de l'objet retourné se produit réellement.GetFooSynchronous
méthode?Task
au lieu de bloquer.réponse rapide: changez cette ligne
à
Pourquoi? vous ne devez pas utiliser .result pour obtenir le résultat des tâches dans la plupart des applications, à l'exception des applications de console, si vous le faites, votre programme se bloquera lorsqu'il y arrivera
vous pouvez également essayer le code ci-dessous si vous souhaitez utiliser .Result
la source