Estoy trabajando con una nueva base de código que hace un uso intensivo de async / wait. La mayoría de las personas en mi equipo también son bastante nuevas en async / wait. En general, tendemos a mantener las mejores prácticas según lo especificado por Microsoft , pero generalmente necesitamos que nuestro contexto fluya a través de la llamada asincrónica y estamos trabajando con bibliotecas que no lo hacen ConfigureAwait(false)
.
Combina todas esas cosas y nos toparemos con bloqueos asincrónicos descritos en el artículo ... semanalmente. No aparecen durante las pruebas unitarias, porque nuestras fuentes de datos simuladas (generalmente a través de Task.FromResult
) no son suficientes para desencadenar el punto muerto. Por lo tanto, durante el tiempo de ejecución o las pruebas de integración, algunas llamadas de servicio simplemente salen a almorzar y nunca regresan. Eso mata a los servidores y, en general, hace un desastre.
El problema es que rastrear dónde se cometió el error (por lo general, simplemente no ser asíncrono hasta el final) generalmente implica la inspección manual del código, que lleva mucho tiempo y no es automatizable.
¿Cuál es una mejor manera de diagnosticar qué causó el punto muerto?
async
artículos de este chico ?Respuestas:
Ok, no estoy seguro si lo siguiente será de alguna ayuda para usted, porque hice algunas suposiciones en el desarrollo de una solución que puede o no ser cierta en su caso. Tal vez mi "solución" es demasiado teórica y solo funciona para ejemplos artificiales: no he hecho ninguna prueba más allá de las siguientes.
Además, vería lo siguiente más una solución alternativa que una solución real, pero teniendo en cuenta la falta de respuestas, creo que aún podría ser mejor que nada (seguí mirando su pregunta esperando una solución, pero al no ver una publicación, comencé a jugar alrededor con el problema).
Pero suficiente dijo: Digamos que tenemos un servicio de datos simple que se puede utilizar para recuperar un número entero:
Una implementación simple usa código asincrónico:
Ahora, surge un problema, si estamos usando el código "incorrectamente" como se ilustra en esta clase.
Foo
accede incorrectamente enTask.Result
lugar de obtenerawait
el resultado como loBar
hace:Lo que necesitamos (usted) ahora es una forma de escribir una prueba que tenga éxito al llamar
Bar
pero falla al llamarFoo
(al menos si entendí la pregunta correctamente ;-)).Dejaré hablar el código; esto es lo que se me ocurrió (usando las pruebas de Visual Studio, pero también debería funcionar usando NUnit):
DataServiceMock
utilizaTaskCompletionSource<T>
. Esto nos permite establecer el resultado en un punto definido en la ejecución de la prueba que conduce a la siguiente prueba. Tenga en cuenta que estamos utilizando un delegado para devolver el TaskCompletionSource a la prueba. También puede poner esto en el método Initialize de la prueba y usar las propiedades.Lo que sucede aquí es que primero verificamos que podemos dejar el método sin bloquear (esto no funcionaría si alguien accediera
Task.Result
; en este caso, tendríamos un tiempo de espera ya que el resultado de la tarea no está disponible hasta después de que el método haya regresado )Luego, establecemos el resultado (ahora el método puede ejecutarse) y verificamos el resultado (dentro de una prueba unitaria podemos acceder a Task.Result ya que realmente queremos que ocurra el bloqueo).
Clase de prueba completa:
BarTest
tiene éxito yFooTest
falla según lo deseado.Y una pequeña clase de ayuda para probar los puntos muertos / tiempos de espera:
fuente
Aquí hay una estrategia que utilicé en una aplicación enorme y muy, muy multiproceso:
Primero, necesita cierta estructura de datos alrededor de un mutex (desafortunadamente) y no hacer un directorio de llamadas de sincronización. En esa estructura de datos, hay un enlace a cualquier mutex previamente bloqueado. Cada mutex tiene un "nivel" que comienza en 0, que se asigna cuando se crea el mutex y nunca puede cambiar.
Y la regla es: si un mutex está bloqueado, solo debe bloquear otros mutex en un nivel inferior. Si sigues esa regla, entonces no puedes tener puntos muertos. Cuando encuentra una infracción, su aplicación aún está funcionando correctamente.
Cuando encuentra una violación, hay dos posibilidades: puede haber asignado los niveles incorrectamente. Bloqueaste A seguido de B, por lo que B debería haber tenido un nivel más bajo. Entonces arreglas el nivel e intentas de nuevo.
La otra posibilidad: no puedes arreglarlo. Algunos códigos tuyos bloquean A seguido de bloquear B, mientras que otros códigos bloquean B seguido de bloquear A. No hay forma de asignar los niveles para permitir esto. Y, por supuesto, este es un punto muerto potencial: si ambos códigos se ejecutan simultáneamente en subprocesos diferentes, existe la posibilidad de un punto muerto.
Después de introducir esto, hubo una fase bastante corta en la que los niveles tuvieron que ser ajustados, seguida de una fase más larga donde se encontraron posibles puntos muertos.
fuente
¿Está utilizando Async / Await para poder paralelizar llamadas costosas como a una base de datos? Dependiendo de la ruta de ejecución en la base de datos, esto podría no ser posible.
La cobertura de prueba con async / await puede ser un desafío y no hay nada como el uso real de producción para encontrar errores. Un patrón que puede considerar es pasar un ID de correlación y registrarlo en la pila, luego tiene un tiempo de espera en cascada que registra el error. Este es más un patrón SOA, pero al menos le daría una idea de dónde viene. Usamos esto con Splunk para encontrar puntos muertos.
fuente