Je travaille avec une nouvelle base de code qui utilise massivement async / wait. La plupart des membres de mon équipe sont également relativement nouveaux dans async / wait. Nous avons généralement tendance à respecter les meilleures pratiques spécifiées par Microsoft , mais nous avons généralement besoin que notre contexte passe par l'appel asynchrone et travaillons avec des bibliothèques qui ne le font pas ConfigureAwait(false)
.
Combinez toutes ces choses et nous rencontrons des blocages asynchrones décrits dans l'article ... hebdomadaire. Ils n'apparaissent pas pendant les tests unitaires, car nos sources de données simulées (généralement via Task.FromResult
) ne sont pas suffisantes pour déclencher l'impasse. Ainsi, lors des tests d'exécution ou d'intégration, certains appels de service sortent pour le déjeuner et ne reviennent jamais. Cela tue les serveurs et fait généralement un gâchis.
Le problème est que la recherche de l'endroit où l'erreur a été commise (généralement pas tout à fait asynchrone) implique généralement une inspection manuelle du code, ce qui prend du temps et n'est pas automatisable.
Quelle est la meilleure façon de diagnostiquer la cause de l'impasse?
async
articles de ce type ?Réponses:
Ok - Je ne sais pas si les éléments suivants vous seront utiles, car j'ai fait certaines hypothèses dans le développement d'une solution qui peut ou non être vraie dans votre cas. Peut-être que ma "solution" est trop théorique et ne fonctionne que pour des exemples artificiels - je n'ai pas fait de test au-delà des choses ci-dessous.
De plus, je verrais ce qui suit plus une solution de contournement qu'une vraie solution, mais étant donné le manque de réponses, je pense que cela pourrait être mieux que rien (j'ai continué à regarder votre question en attendant une solution, mais sans en voir une se publier, j'ai commencé à jouer autour de la question).
Mais assez dit: disons que nous avons un service de données simple qui peut être utilisé pour récupérer un entier:
Une implémentation simple utilise du code asynchrone:
Maintenant, un problème se pose, si nous utilisons le code "incorrectement" comme illustré par cette classe.
Foo
accède de manière incorrecteTask.Result
au lieu deawait
générer le résultat comme leBar
fait:Ce dont nous (vous) avons maintenant besoin, c'est d'un moyen d'écrire un test qui réussit lors de l'appel
Bar
mais échoue lors de l'appelFoo
(du moins si j'ai bien compris la question ;-)).Je vais laisser le code parler; voici ce que j'ai trouvé (en utilisant des tests Visual Studio, mais cela devrait aussi fonctionner en utilisant NUnit):
DataServiceMock
utiliseTaskCompletionSource<T>
. Cela nous permet de définir le résultat à un point défini dans le test, ce qui conduit au test suivant. Notez que nous utilisons un délégué pour renvoyer le TaskCompletionSource dans le test. Vous pouvez également mettre cela dans la méthode Initialize des propriétés test et use.Ce qui se passe ici, c'est que nous vérifions d'abord que nous pouvons laisser la méthode sans bloquer (cela ne fonctionnerait pas si quelqu'un y accédait
Task.Result
- dans ce cas, nous courrions dans un délai d'attente car le résultat de la tâche n'est rendu disponible qu'après le retour de la méthode ).Ensuite, nous définissons le résultat (maintenant la méthode peut s'exécuter) et nous vérifions le résultat (à l'intérieur d'un test unitaire, nous pouvons accéder à Task.Result car nous voulons réellement que le blocage se produise).
Classe de test complète -
BarTest
réussit etFooTest
échoue comme souhaité.Et une petite classe d'aide pour tester les blocages / timeouts:
la source
Voici une stratégie que j'ai utilisée dans une application énorme et très, très multithread:
Tout d'abord, vous avez besoin d'une structure de données autour d'un mutex (malheureusement) et ne faites pas de répertoire d'appels de synchronisation. Dans cette structure de données, il existe un lien vers tout mutex précédemment verrouillé. Chaque mutex a un "niveau" commençant à 0, que vous attribuez lorsque le mutex est créé et ne peut jamais changer.
Et la règle est la suivante: si un mutex est verrouillé, vous ne devez verrouiller que d'autres mutex à un niveau inférieur. Si vous suivez cette règle, vous ne pouvez pas avoir de blocages. Lorsque vous constatez une violation, votre application est toujours en place et fonctionne très bien.
Lorsque vous constatez une violation, il y a deux possibilités: vous avez peut-être mal attribué les niveaux. Vous avez verrouillé A suivi de verrouiller B, donc B aurait dû avoir un niveau inférieur. Vous fixez donc le niveau et réessayez.
L'autre possibilité: vous ne pouvez pas le réparer. Certains de vos codes verrouillent A suivi du verrouillage B, tandis que d'autres codes verrouillent B suivis du verrouillage A. Il n'y a aucun moyen d'attribuer les niveaux pour permettre cela. Et bien sûr, c'est un blocage potentiel: si les deux codes s'exécutent simultanément sur des threads différents, il y a un risque de blocage.
Après avoir introduit cela, il y a eu une phase assez courte où les niveaux ont dû être ajustés, suivie d'une phase plus longue où des blocages potentiels ont été trouvés.
la source
Utilisez-vous Async / Await pour pouvoir paralléliser des appels coûteux comme vers une base de données? Selon le chemin d'exécution dans la base de données, cela peut ne pas être possible.
La couverture des tests avec async / wait peut être difficile et il n'y a rien de tel qu'une utilisation réelle de la production pour trouver des bogues. Un modèle que vous pouvez envisager est de passer un ID de corrélation et de le journaliser dans la pile, puis d'avoir un délai d'expiration en cascade qui enregistre l'erreur. Il s'agit plus d'un modèle SOA, mais au moins, cela vous donnerait une idée de son origine. Nous l'avons utilisé avec Splunk pour trouver des blocages.
la source