Esta pregunta se refiere al lenguaje C #, pero espero que cubra otros lenguajes como Java o TypeScript.
Microsoft recomienda las mejores prácticas sobre el uso de llamadas asincrónicas en .NET. Entre estas recomendaciones, escojamos dos:
- cambiar la firma de los métodos asíncronos para que devuelvan Tarea o Tarea <> (en TypeScript, eso sería una Promesa <>)
- cambiar los nombres de los métodos asíncronos para terminar con xxxAsync ()
Ahora, cuando se reemplaza un componente síncrono de bajo nivel por uno asíncrono, esto afecta la pila completa de la aplicación. Dado que async / await tiene un impacto positivo solo si se usa "completamente", significa que se deben cambiar los nombres de firma y método de cada capa de la aplicación.
Una buena arquitectura a menudo implica colocar abstracciones entre cada capa, de modo que el reemplazo de componentes de bajo nivel por otros no sea visto por los componentes de nivel superior. En C #, las abstracciones toman la forma de interfaces. Si presentamos un nuevo componente asíncrono de bajo nivel, cada interfaz en la pila de llamadas debe ser modificada o reemplazada por una nueva interfaz. La forma en que se resuelve un problema (asíncrono o sincronización) en una clase de implementación ya no se oculta (abstrae) a las personas que llaman. Las personas que llaman tienen que saber si es sincronizado o asíncrono.
¿Acaso las mejores prácticas asíncronas / en espera no contradicen los principios de "buena arquitectura"?
¿Significa que cada interfaz (por ejemplo, IEnumerable, IDataAccessLayer) necesita su contraparte asíncrona (IAsyncEnumerable, IAsyncDataAccessLayer) de modo que puedan reemplazarse en la pila al cambiar a dependencias asíncronas?
Si llevamos el problema un poco más lejos, ¿no sería más sencillo asumir que todos los métodos son asíncronos (para devolver una Tarea <> o Promesa <>), y que los métodos sincronicen las llamadas asíncronas cuando en realidad no están asíncrono? ¿Es esto algo que se espera de los futuros lenguajes de programación?
fuente
CancellationToken
, y aquellos que lo hacen pueden desear proporcionar un valor predeterminado). La eliminación de los métodos de sincronización existentes (y la ruptura proactiva de todo el código) es un obvio obvio.Respuestas:
¿De qué color es tu función?
Quizás te interese Bob Nystrom's What Color Is Your Function 1 .
En este artículo, describe un lenguaje ficticio donde:
Si bien es ficticio, esto sucede con bastante frecuencia en los lenguajes de programación:
this
.Como se habrá dado cuenta, debido a estas reglas, las funciones rojas tienden a extenderse por la base del código. Inserta uno y, poco a poco, coloniza todo el código base.
1 Bob Nystrom, además de bloguear, también es parte del equipo Dart y ha escrito esta pequeña serie de Crafting Interpreters; Muy recomendable para cualquier lenguaje de programación / compilador afficionado.
2 No es del todo cierto, ya que puede llamar a una función asíncrona y bloquear hasta que regrese, pero ...
Limitación de lenguaje
Esto es, esencialmente, una limitación de idioma / tiempo de ejecución.
El lenguaje con subprocesos M: N, por ejemplo, como Erlang y Go, no tiene
async
funciones: cada función es potencialmente asíncrona y su "fibra" simplemente se suspenderá, se intercambiará y se volverá a activar cuando esté lista nuevamente.C # optó por un modelo de subprocesos 1: 1 y, por lo tanto, decidió sacar a la superficie la sincronía en el lenguaje para evitar el bloqueo accidental de subprocesos.
En presencia de limitaciones de lenguaje, las pautas de codificación deben adaptarse.
fuente
async
); de hecho, era un patrón bastante común para construir una interfaz de usuario receptiva. ¿Era tan bonita como Erlang? No. Pero eso sigue muy lejos de C :)Tienes razón, hay una contradicción aquí, pero no es que las "mejores prácticas" sean malas. Esto se debe a que la función asincrónica hace algo esencialmente diferente que una síncrona. En lugar de esperar el resultado de sus dependencias (generalmente algunas E / S), crea una tarea que debe manejar el bucle de eventos principal. Esta no es una diferencia que pueda estar bien oculta bajo abstracción.
fuente
async
métodos en C # para proporcionar contratos sincrónicos también, si así lo desea. La única diferencia es que Go es asíncrono por defecto, mientras que C # es sincrónico por defecto.async
le ofrece el segundo modelo de programación:async
es la abstracción (lo que realmente hace depende del tiempo de ejecución, el programador de tareas, el contexto de sincronización, la implementación del camarero ...).Un método asincrónico se comporta de manera diferente a uno que es sincrónico, como estoy seguro de que sabe. En tiempo de ejecución, convertir una llamada asíncrona en una sincrónica es trivial, pero no se puede decir lo contrario. Por lo tanto, la lógica se convierte entonces, ¿por qué no hacemos métodos asíncronos de cada método que lo requiera y dejamos que la persona que llama "convierta" según sea necesario a un método sincrónico?
En cierto sentido, es como tener un método que arroja excepciones y otro que es "seguro" y no arrojará incluso en caso de error. ¿En qué punto es excesivo el codificador para proporcionar estos métodos que de otro modo se pueden convertir uno a otro?
En esto hay dos escuelas de pensamiento: una es crear múltiples métodos, cada uno de los cuales llama a otro método posiblemente privado que permite la posibilidad de proporcionar parámetros opcionales o alteraciones menores al comportamiento, como ser asíncrono. El otro es minimizar los métodos de interfaz para mostrar lo esencial, dejando que la persona que llama realice las modificaciones necesarias por sí mismo.
Si eres de la primera escuela, hay una cierta lógica en dedicar una clase a llamadas síncronas y asíncronas para evitar duplicar cada llamada. Microsoft tiende a favorecer esta escuela de pensamiento y, por convención, para mantenerse consistente con el estilo favorecido por Microsoft, usted también debería tener una versión Async, de la misma manera que las interfaces casi siempre comienzan con un "I". Permítanme enfatizar que no está mal , per se, porque es mejor mantener un estilo consistente en un proyecto en lugar de hacerlo "de la manera correcta" y cambiar radicalmente el estilo para el desarrollo que agrega a un proyecto.
Dicho esto, tiendo a favorecer la segunda escuela, que es minimizar los métodos de interfaz. Si creo que un método puede llamarse de forma asincrónica, el método para mí es asincrónico. La persona que llama puede decidir si esperar o no a que termine esa tarea antes de continuar. Si esta interfaz es una interfaz para una biblioteca, es más razonable hacerlo de esta manera para minimizar la cantidad de métodos que necesitaría desaprobar o ajustar. Si la interfaz es para uso interno en mi proyecto, agregaré un método para cada llamada necesaria en mi proyecto para los parámetros proporcionados y no hay métodos "adicionales", e incluso entonces, solo si el comportamiento del método no está cubierto por un método existente.
Sin embargo, como muchas cosas en este campo, es en gran parte subjetivo. Ambos enfoques tienen sus pros y sus contras. Microsoft también comenzó la convención de agregar letras indicativas de tipo al comienzo del nombre de la variable y "m_" para indicar que es un miembro, lo que lleva a nombres de variables como
m_pUser
. Mi punto es que ni siquiera Microsoft es infalible, y también puede cometer errores.Dicho esto, si su proyecto sigue esta convención Async, le aconsejaría que lo respete y continúe con el estilo. Y solo una vez que tenga un proyecto propio, puede escribirlo de la mejor manera que mejor le parezca.
fuente
.Wait()
métodos y similares puede causar consecuencias negativas, y en js, que yo sepa, no es posible en absoluto.Promise.resolve(x)
y luego le agrega devoluciones de llamada, esas devoluciones de llamada no se ejecutarán de inmediato.Imaginemos que hay una manera de permitirle llamar a las funciones de forma asíncrona sin cambiar su firma.
Eso sería genial y nadie recomendaría que cambies sus nombres.
Pero, las funciones asincrónicas reales, no solo las que esperan otra función asincrónica, sino que el nivel más bajo tiene alguna estructura específica para ellas según su naturaleza asincrónica. p.ej
Es los dos bits de información que necesita el código de llamada con el fin de ejecutar el código de forma asíncrona que cosas como
Task
yasync
encapsular.Hay más de una forma de hacer funciones asincrónicas,
async Task
es solo un patrón integrado en el compilador a través de tipos de retorno para que no tenga que vincular manualmente los eventosfuente
Abordaré el punto principal de una manera menos C # ness y más genérica:
Diría que solo depende de la elección que haga en el diseño de su API y de lo que le permita al usuario.
Si desea que una función de su API sea solo asíncrona, hay poco interés en seguir la convención de nomenclatura. Simplemente siempre devuelva la Tarea <> / Promesa <> / Futuro <> / ... como tipo de retorno, se auto documenta. Si quiere una respuesta sincronizada, aún podrá hacerlo esperando, pero si siempre lo hace, será un poco repetitivo.
Sin embargo, si realiza solo su sincronización API, eso significa que si un usuario quiere que sea asíncrono, tendrá que administrar la parte asíncrona de él mismo.
Esto puede hacer mucho trabajo extra, sin embargo, también puede dar más control al usuario sobre cuántas llamadas simultáneas permite, tiempo de espera, devoluciones, etc.
En un sistema grande con una API enorme, implementar la mayoría de ellos para que se sincronicen de manera predeterminada podría ser más fácil y más eficiente que administrar de forma independiente cada parte de su API, especialmente si comparten recursos (sistema de archivos, CPU, base de datos, ...).
De hecho, para las partes más complejas, podría tener perfectamente dos implementaciones de la misma parte de su API, una sincrónica haciendo las cosas útiles, una asincrónica confiando en la sincrónica sí maneja las cosas y solo administra la concurrencia, las cargas, los tiempos de espera y los reintentos .
Quizás alguien más pueda compartir su experiencia con eso porque me falta experiencia con tales sistemas.
fuente