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:
¿Vi que la gente dice que puedes usar GetAwaiter().GetResult()
un método asíncrono e invocarlo desde tu método de sincronización? ¿Es seguro ese hilo en todos los escenarios?
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:
- Solicite una nueva cuchilla de un sitio web. Esta es una operación asíncrona de alta latencia.
- Sincrónicamente espere, es decir, duerma hasta que tenga la cuchilla en la mano .
- Revise periódicamente el buzón para ver si ha llegado la hoja.
- Retire la cuchilla de la caja. Ahora lo tienes en la mano.
- Instale la cuchilla en el cortacésped.
- Cortar el césped.
¿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.