Estoy trabajando en un proyecto que necesita admitir versiones asíncronas y sincronizadas de una misma lógica / método. Entonces, por ejemplo, necesito tener:
public class Foo
{
public bool IsIt()
{
using (var conn = new SqlConnection(DB.ConnString))
{
return conn.Query<bool>("SELECT IsIt FROM SomeTable");
}
}
public async Task<bool> IsItAsync()
{
using (var conn = new SqlConnection(DB.ConnString))
{
return await conn.QueryAsync<bool>("SELECT IsIt FROM SomeTable");
}
}
}
La lógica asíncrona y de sincronización para estos métodos es idéntica en todos los aspectos, excepto que uno es asíncrono y el otro no. ¿Hay alguna forma legítima de evitar violar el principio DRY en este tipo de escenario? Vi que la gente dice que puedes usar GetAwaiter (). GetResult () en un método asíncrono e invocarlo desde tu método de sincronización. ¿Es seguro ese hilo en todos los escenarios? ¿Hay otra forma mejor de hacer esto o me veo obligado a duplicar la lógica?
c#
.net
asynchronous
Marko
fuente
fuente
return Task.FromResult(IsIt());
)Respuestas:
Hiciste varias preguntas en tu pregunta. Los desglosaré de manera ligeramente diferente a como lo hiciste tú. Pero primero déjame responder directamente a la pregunta.
Todos queremos una cámara que sea liviana, de alta calidad y barata, pero como dice el dicho, solo puede obtener como máximo dos de esos tres. Estás en la misma situación aquí. Desea una solución que sea eficiente, segura y que comparta código entre las rutas síncronas y asíncronas. Solo obtendrás dos de esos.
Déjame desglosar por qué es eso. Comenzaremos con esta pregunta:
El punto de esta pregunta es "¿puedo compartir las rutas sincrónicas y asincrónicas haciendo que la ruta sincrónica simplemente haga una espera sincrónica en la versión asincrónica?"
Permítanme ser muy claro en este punto porque es importante:
DEBES DEJAR DE INMEDIATO TOMAR CUALQUIER CONSEJO DE AQUELLAS PERSONAS .
Ese es un consejo extremadamente malo. Es muy peligroso obtener sincrónicamente un resultado de una tarea asincrónica a menos que tenga evidencia de que la tarea se ha completado normal o anormalmente .
La razón por la que este es un consejo extremadamente malo es, bueno, considere este escenario. Desea cortar el césped, pero la cuchilla del cortacésped está rota. Decide seguir este flujo de trabajo:
¿Lo que pasa? Duermes para siempre porque la operación de revisar el correo ahora está cerrada en algo que sucede después de que llega el correo .
Es extremadamente fácil entrar en esta situación cuando espera sincrónicamente una tarea arbitraria. Esa tarea podría tener un trabajo programado en el futuro del hilo que ahora está esperando , y ahora ese futuro nunca llegará porque lo estás esperando.
Si haces una espera asincrónica, ¡ entonces todo está bien! Revisas periódicamente el correo y, mientras esperas, haces un sándwich o haces tus impuestos o lo que sea; sigues haciendo el trabajo mientras esperas.
Nunca sincrónicamente espere. Si la tarea está hecha, es innecesaria . Si la tarea no se realiza pero está programada para ejecutarse en el subproceso actual, es ineficiente porque el subproceso actual podría estar sirviendo a otro trabajo en lugar de esperar. Si la tarea no se realiza y la ejecución programada en el subproceso actual, se bloquea para esperar sincrónicamente. No hay una buena razón para esperar sincrónicamente, de nuevo, a menos que ya sepa que la tarea está completa .
Para leer más sobre este tema, vea
https://blog.stephencleary.com/2012/07/dont-block-on-async-code.html
Stephen explica el escenario del mundo real mucho mejor que yo.
Ahora consideremos la "otra dirección". ¿Podemos compartir código haciendo que la versión asincrónica simplemente haga la versión sincrónica en un subproceso de trabajo?
Es posible y, de hecho, probablemente una mala idea, por las siguientes razones.
Es ineficiente si la operación síncrona es un trabajo de E / S de alta latencia. Esto esencialmente contrata a un trabajador y lo hace dormir hasta que se realiza una tarea. Los hilos son increíblemente caros . Consumen un millón de bytes de espacio mínimo de direcciones por defecto, toman tiempo, toman recursos del sistema operativo; no quieres quemar un hilo haciendo un trabajo inútil.
La operación síncrona podría no estar escrita para ser segura para subprocesos.
Esta es una técnica más razonable si el trabajo de alta latencia está vinculado al procesador, pero si es así, probablemente no desee simplemente pasarlo a un subproceso de trabajo. Es probable que desee utilizar la biblioteca paralela de tareas para paralelizarla a tantas CPU como sea posible, probablemente desee la lógica de cancelación y no puede simplemente hacer que la versión síncrona haga todo eso, porque entonces ya sería la versión asíncrona .
Otras lecturas; nuevamente, Stephen lo explica muy claramente:
Por qué no usar Task.Run:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-using.html
Más escenarios de "hacer y no hacer" para Task.Run:
https://blog.stephencleary.com/2013/11/taskrun-etiquette-examples-dont-use.html
¿Con qué nos deja eso? Ambas técnicas para compartir código conducen a puntos muertos o grandes ineficiencias. La conclusión a la que llegamos es que debe tomar una decisión. ¿Desea un programa que sea eficiente y correcto y que deleite a la persona que llama, o desea guardar algunas pulsaciones de teclas implicadas al duplicar una pequeña cantidad de código entre las rutas síncronas y asíncronas? No entiendes las dos, me temo.
fuente
Task.Run(() => SynchronousMethod())
en la versión asíncrona no es lo que quiere el OP?Dns.GetHostEntryAsync
es como se implementa en .NET, yFileStream.ReadAsync
para ciertos tipos de archivos. El sistema operativo simplemente no proporciona una interfaz asíncrona, por lo que el tiempo de ejecución tiene que fingirlo (y no es específico del idioma; por ejemplo, el tiempo de ejecución de Erlang ejecuta todo el árbol de procesos de trabajo , con múltiples hilos dentro de cada uno, para proporcionar E / S de disco sin bloqueo y nombre resolución).Es difícil dar una respuesta única para todos. Desafortunadamente, no hay una manera simple y perfecta de reutilizar el código asíncrono y síncrono. Pero aquí hay algunos principios a considerar:
Query()
en uno yQueryAsync()
en el otro), o configurando conexiones con diferentes configuraciones. Entonces, incluso cuando es estructuralmente similar, a menudo hay suficientes diferencias de comportamiento para merecer que se traten como un código separado con diferentes requisitos. Tenga en cuenta las diferencias entre implementaciones de métodos Async y Sync en la clase File, por ejemplo: no se hace ningún esfuerzo para que usen el mismo códigoTask.FromResult(...)
.Buena suerte.
fuente
Fácil; haga que el síncrono llame al asíncrono. Incluso hay un método útil
Task<T>
para hacer exactamente eso:fuente