En la programación de computadoras , el patrón asincrónico / Await es una función sintáctica de muchos lenguajes de programación que permite a un asincrónica , no bloqueando la función a ser estructurado de manera similar a una función sincrónica ordinaria. Está semánticamente relacionado con el concepto de una corrutina y a menudo se implementa utilizando técnicas similares, y su objetivo principal es brindar oportunidades para que el programa ejecute otro código mientras espera que se complete una tarea asincrónica de larga ejecución, generalmente representada por promesas o estructuras de datos similares. La función se encuentra en C # 5.0 , Python 3.5, F # ,Hack , Dart , Kotlin 1.1, Rust 1.39, [1] Nim 0.9.4 [2] y JavaScript ES2017 , con algunos trabajos experimentales en extensiones, versiones beta e implementaciones particulares de Scala [3] y C ++ . También está previsto para Swift . [ cita requerida ]
Historia
F # agregó flujos de trabajo asíncronos con puntos de espera en la versión 2.0 en 2007. [4] Esto influyó en el mecanismo asíncrono / espera agregado a C #. [5]
Microsoft lanzó una versión de C # con async / await por primera vez en Async CTP (2011). Y luego se lanzaron oficialmente en C # 5 (2012). [6]
El desarrollador principal de Haskell, Simon Marlow, creó el paquete asincrónico en 2012. [7]
Python agregó soporte para async / await con la versión 3.5 en 2015 [8] con 2 nuevas palabras clave async y await.
TypeScript agregó soporte para async / await con la versión 1.7 en 2015. [9]
Javascript agregó soporte para async / await en 2017 como parte de la edición de JavaScript ECMAScript 2017.
Rust agregó soporte para async / await con la versión 1.39.0 en 2019 [10] con 1 nueva palabra clave async y un patrón de espera de evaluación perezosa. [11]
C ++ agregó soporte para async / await con la versión 20 en 2020 con 3 nuevas palabras clave co_return, co_await, co_yield
Ejemplo C #
La función C # a continuación, que descarga un recurso de un URI y devuelve la longitud del recurso, usa este patrón asíncrono / espera:
Tarea asíncrona pública < int > FindPageSizeAsync ( Uri uri ) { cliente var = new HttpClient (); byte [] datos = aguardar cliente . GetByteArrayAsync ( uri ); devolver datos . Longitud ; }
- Primero, la
async
palabra clave le indica a C # que el método es asíncrono, lo que significa que puede usar un número arbitrario deawait
expresiones y vinculará el resultado a una promesa . - El tipo de retorno
Task
,, es análogo de C # al concepto de promesa, y aquí se indica que tiene un valor de resultado de tipoint
. - La primera expresión que se ejecutará cuando se llame a este método será
new HttpClient().GetByteArrayAsync(uri)
, que es otro método asincrónico que devuelve unTask
. Debido a que este método es asincrónico, no descargará todo el lote de datos antes de regresar. En su lugar, comenzará el proceso de descarga utilizando un mecanismo sin bloqueo (como un hilo en segundo plano ) e inmediatamente devolverá un mensaje sin resolver y sin rechazar[]> Task
a esta función.[]> - Con la
await
palabra clave adjunta aTask
, esta función procederá inmediatamente a devolver unTask
a su llamador, quien podrá continuar con otro procesamiento según sea necesario. - Una vez que
GetByteArrayAsync()
finalice su descarga, resolverá laTask
devolución con los datos descargados. Esto activará una devolución de llamada y haráFindPageSizeAsync()
que continúe la ejecución al asignar ese valor adata
. - Finalmente, el método devuelve
data.Length
un entero simple que indica la longitud de la matriz. El compilador reinterpreta esto como una resolución de loTask
que devolvió anteriormente, lo que desencadenó una devolución de llamada en el llamador del método para hacer algo con ese valor de longitud.
Una función que usa async / await puede usar tantas await
expresiones como desee, y cada una se manejará de la misma manera (aunque una promesa solo se devolverá a la persona que llama para la primera espera, mientras que todas las demás esperan utilizarán devoluciones de llamada internas). Una función también puede contener un objeto de promesa directamente y realizar otro procesamiento primero (incluido el inicio de otras tareas asincrónicas), retrasando la espera de la promesa hasta que se necesite su resultado. Las funciones con promesas también tienen métodos de agregación de promesas que le permiten esperar múltiples promesas a la vez o en algún patrón especial (como C # Task.WhenAll()
, que devuelve un valor sin valor Task
que se resuelve cuando todas las tareas en los argumentos se han resuelto). Muchos tipos de promesa también tienen características adicionales más allá de lo que normalmente usa el patrón async / await, como poder configurar más de una devolución de llamada de resultado o inspeccionar el progreso de una tarea de ejecución especialmente larga.
En el caso particular de C #, y en muchos otros lenguajes con esta característica de lenguaje, el patrón async / await no es una parte central del tiempo de ejecución del lenguaje, sino que se implementa con lambdas o continuaciones en tiempo de compilación. Por ejemplo, el compilador de C # probablemente traduciría el código anterior a algo como lo siguiente antes de traducirlo a su formato de código de bytes IL :
Tarea pública < int > FindPageSizeAsync ( Uri uri ) { cliente var = new HttpClient (); Tarea < byte []> data_task = cliente . GetByteArrayAsync ( uri ); Tarea < int > after_data_task = data_task . ContinueWith (( original_task ) => { return original_task . Resultado . Longitud ; }); return after_data_task ; }
Debido a esto, si un método de interfaz necesita devolver un objeto de promesa, pero en sí mismo no requiere que await
el cuerpo espere en ninguna tarea asincrónica, tampoco necesita el async
modificador y, en su lugar, puede devolver un objeto de promesa directamente. Por ejemplo, una función puede proporcionar una promesa que se resuelve inmediatamente en algún valor de resultado (como C # Task.FromResult()
), o puede simplemente devolver la promesa de otro método que resulta ser la promesa exacta necesaria (como cuando se aplaza a una sobrecarga ) .
Sin embargo, una advertencia importante de esta funcionalidad es que, si bien el código se parece al código de bloqueo tradicional, el código en realidad no es bloqueante y es potencialmente multiproceso, lo que significa que pueden ocurrir muchos eventos intermedios mientras se espera await
que se resuelva la promesa dirigida por un . Por ejemplo, el siguiente código, aunque siempre tiene éxito en un modelo de bloqueo sin await
, puede experimentar eventos intermedios durante el await
y, por lo tanto, puede encontrar el estado compartido cambiado desde debajo de él:
var a = estado . a ; var client = new HttpClient (); var data = aguardar al cliente . GetByteArrayAsync ( uri ); Depurar . Assert ( un == estado . Una ); // Fallo potencial, ya que el valor de state.a puede haber sido cambiado // por el manejador del evento potencialmente interviniente. devolver datos . Longitud ;
En F #
F # agregó flujos de trabajo asincrónicos en la versión 2.0. [12] Los flujos de trabajo asincrónicos se implementan como CE ( expresiones de cálculo ). Se pueden definir sin especificar ningún contexto especial (como async
en C #). Los flujos de trabajo asincrónicos de F # añaden una explosión (!) A las palabras clave para iniciar tareas asincrónicas.
La siguiente función asíncrona descarga datos de una URL mediante un flujo de trabajo asíncrono:
let asyncSumPageSizes ( uris : # seq < Uri >) : Async < int > = async { use httpClient = new HttpClient () ¡dejar! páginas = uris |> Seq . mapa ( httpClient . GetStringAsync >> Async . AwaitTask ) |> Async . Paralelo regresar páginas |> Seq . pliegue ( corriente del acumulador divertido -> corriente . Longitud + acumulador ) 0 }
C ª#
El patrón asíncrono / espera en C # está disponible a partir de la versión 5.0, al que Microsoft se refiere como patrón asíncrono basado en tareas (TAP). [13] se requieren métodos asincrónicos para volver o bien void
, Task
, Task
, o ValueTask
(este último partir de la versión 7.0 solamente). Los métodos asincrónicos que regresan void
están pensados para controladores de eventos ; en la mayoría de los casos en los que se devolvería un método síncrono void
, Task
se recomienda volver en su lugar, ya que permite un manejo de excepciones más intuitivo. [14]
Los métodos que hacen uso await
deben declararse con la async
palabra clave. En los métodos que tienen un valor de retorno de tipo Task
, los métodos declarados con async
deben tener una declaración de retorno de tipo asignable a en T
lugar de Task
; el compilador envuelve el valor en el Task
genérico. También es posible utilizar await
métodos que tienen un tipo de retorno de Task
o Task
que se declaran sin async
.
El siguiente método asíncrono descarga datos de una URL usando await
.
Tarea asíncrona pública < int > SumPageSizesAsync ( ICollection < Uri > uris ) { cliente var = new HttpClient (); int total = 0 ; foreach ( var uri en uris ) { statusText . Text = $ "Encontrados {total} bytes ..." ; var data = aguardar al cliente . GetByteArrayAsync ( uri ); total + = datos . Longitud ; } statusText . Texto = $ "Total de bytes encontrados {total}" ; retorno total ; }
En Scala
En la extensión experimental Scala-async de Scala, await
es un "método", aunque no funciona como un método ordinario. Además, a diferencia de C # 5.0, en el que un método debe marcarse como asíncrono, en Scala-async, un bloque de código está rodeado por una "llamada" asíncrona.
Cómo funciona
En Scala-async, en async
realidad se implementa usando una macro Scala, lo que hace que el compilador emita un código diferente y produzca una implementación de máquina de estado finito (que se considera más eficiente que una implementación monádica , pero menos conveniente de escribir a mano) .
Hay planes para que Scala-async admita una variedad de implementaciones diferentes, incluidas las no asincrónicas.
En Python
Python 3.5 ha agregado soporte para async / await como se describe en PEP 492 ( https://www.python.org/dev/peps/pep-0492/ ).
importar asyncioasync def main (): print ( "hola" ) aguarda asyncio . dormir ( 1 ) imprimir ( "mundo" )asyncio . ejecutar ( main ())
En JavaScript
El operador de espera en JavaScript solo se puede usar desde dentro de una función asíncrona. Si el parámetro es una promesa , la ejecución de la función asíncrona se reanudará cuando se resuelva la promesa (a menos que se rechace la promesa, en cuyo caso se generará un error que se puede manejar con el manejo normal de excepciones de JavaScript ). Si el parámetro no es una promesa, el parámetro en sí se devolverá inmediatamente. [15]
Muchas bibliotecas proporcionan objetos de promesa que también se pueden usar con await, siempre que coincidan con la especificación de las promesas de JavaScript nativas. Sin embargo, las promesas de la biblioteca jQuery no eran compatibles con Promises / A + hasta jQuery 3.0. [dieciséis]
Aquí hay un ejemplo (modificado de este artículo [17] ):
función asíncrona createNewDoc () { dejar respuesta = esperar db . publicar ({}); // publica un nuevo documento return await db . obtener ( respuesta . id ); // buscar por id }asíncrono función principal () { try { dejar doc = Await createNewDoc (); consola . log ( doc ); } captura ( err ) { consola . log ( err ); } } principal ();
La versión 8 de Node.js incluye una utilidad que permite usar los métodos de devolución de llamada de la biblioteca estándar como promesas. [18]
En C ++
En C ++, await (llamado co_await en C ++) se ha fusionado oficialmente en el borrador de C ++ 20, por lo que está en camino de ser aceptado formalmente como parte del C ++ 20 oficial; [19] también los compiladores de MSVC y Clang ya están soportando al menos alguna forma de co_await ( GCC todavía no tiene soporte para ello).
#include #include usando el espacio de nombres std ;futuro < int > add ( int a , int b ) { int c = a + b ; co_return c ; }future < void > test () { int ret = co_await add ( 1 , 2 ); cout << "volver" << ret << endl ; }int main () { auto fut = test (); fut . esperar (); return 0 ; }
C ª
Todavía no hay soporte oficial para await / async en el lenguaje C. Algunas bibliotecas de rutinas como s_task simulan las palabras clave await / async con macros.
#include #include "s_task.h"// define la memoria de pila para tareas int g_stack_main [ 64 * 1024 / sizeof ( int )]; int g_stack0 [ 64 * 1024 / sizeof ( int )]; int g_stack1 [ 64 * 1024 / sizeof ( int )];void sub_tarea ( __async__ , void * arg ) { int i ; int n = ( int ) ( tamaño_t ) arg ; for ( i = 0 ; i < 5 ; ++ i ) { printf ( "tarea% d, retardo segundos =% d, i =% d \ n " , n , n , i ); s_task_msleep ( __await__ , n * 1000 ); // s_task_yield (__ await__); } }void tarea_principal ( __async__ , void * arg ) { int i ; // crea dos subtareas s_task_create ( g_stack0 , sizeof ( g_stack0 ), sub_task , ( void * ) 1 ); s_task_create ( g_stack1 , sizeof ( g_stack1 ), sub_tarea , ( void * ) 2 ); for ( i = 0 ; i < 4 ; ++ i ) { printf ( "task_mainarg =% p, i =% d \ n " , arg , i ); s_task_yield ( __await__ ); } // espera las subtareas para salir s_task_join ( __await__ , g_stack0 ); s_task_join ( __await__ , g_stack1 ); }int main ( int argc , char * argv ) { s_task_init_system (); // crea la tarea principal s_task_create ( g_stack_main , sizeof ( g_stack_main ), main_task , ( void * ) ( size_t ) argc ); s_task_join ( __await__ , g_stack_main ); printf ( "toda la tarea ha terminado \ n " ); return 0 ; }
En Perl 5
El módulo Future :: AsyncAwait fue objeto de una subvención de la Fundación Perl en septiembre de 2018. [20]
En óxido
El 7 de noviembre de 2019, se lanzó async / await en la versión estable de Rust. [21] Las funciones asíncronas en Rust se refieren a funciones simples que devuelven valores que implementan el rasgo Futuro. Actualmente se implementan con una máquina de estados finitos . [22]
// En el Cargo.toml de la caja, necesitamos `futures =" 0.3.0 "` en la sección de dependencias, // para que podamos usar la caja de futurosfuturos de cajas externas ; // No hay ningún ejecutor actualmente en la biblioteca `std`. // Esto se convierte en algo como // `fn async_add_one (num: u32) -> impl Future async fn async_add_one ( num : u32 ) -> u32 { num + 1 }async fn example_task () { let number = async_add_one ( 5 ). Aguarde ; println! ( "5 + 1 = {}" , número ); }fn main () { // La creación del futuro no inicia la ejecución. dejar futuro = ejemplo_tarea ( 5 ); // El `Future` solo se ejecuta cuando realmente lo sondeamos, a diferencia de Javascript. futuros :: ejecutor :: block_on ( futuro ); }
Beneficios y críticas
Un beneficio significativo del patrón async / await en los lenguajes que lo admiten es que se puede escribir código asincrónico, sin bloqueo, con una sobrecarga mínima y con un aspecto casi como el código de bloqueo sincrónico tradicional. En particular, se ha argumentado que await es la mejor forma de escribir código asincrónico en programas de paso de mensajes ; en particular, al estar cerca del código de bloqueo, la legibilidad y la cantidad mínima de código repetitivo se mencionaron como beneficios pendientes. [23] Como resultado, async / await hace que sea más fácil para la mayoría de los programadores razonar sobre sus programas, y await tiende a promover un código mejor y más robusto sin bloqueo en las aplicaciones que lo requieren. Dichas aplicaciones van desde programas que presentan interfaces gráficas de usuario hasta programas del lado del servidor de estado masivamente escalables, como juegos y aplicaciones financieras.
Al criticar a await, se ha observado que await tiende a hacer que el código circundante también sea asincrónico; por otro lado, se ha argumentado que esta naturaleza contagiosa del código (a veces comparado con un "virus zombie") es inherente a todo tipo de programación asincrónica, por lo que await como tal no es único en este sentido. [14]
Ver también
- Corutinas
- Estilo de continuación-pase
- Estilo directo
- Multitarea cooperativa
Referencias
- ^ "Anunciando Rust 1.39.0" . Consultado el 7 de noviembre de 2019 .
- ^ "Versión 0.9.4 publicada - Blog de Nim" . Consultado el 19 de enero de 2020 .
- ^ "Scala Async" . Consultado el 20 de octubre de 2013 .
- ^ "El modelo de programación asincrónica de F #" . Springer Link . Consultado el 29 de abril de 2021 .
- ^ "La historia temprana de F #, HOPL IV" . Biblioteca digital ACM . Consultado el 29 de abril de 2021 .
- ^ Hejlsberg, Anders. "Anders Hejlsberg: introducción asincrónica - simplificación de la programación asincrónica" . Canal 9 MSDN . Microsoft . Consultado el 5 de enero de 2021 .
- ^ "async: Ejecute operaciones IO de forma asincrónica y espere sus resultados" . Hackage .
- ^ "Novedades de Python 3.5 - Documentación de Python 3.9.1" . docs.python.org . Consultado el 5 de enero de 2021 .
- ^ Gaurav, Seth (30 de noviembre de 2015). "Anuncio de TypeScript 1.7" . TypeScript . Microsoft . Consultado el 5 de enero de 2021 .
- ^ Matsakis, Niko. "¡Async-await en el estable de Rust! | Blog de Rust" . blog.rust-lang.org . Blog de óxido . Consultado el 5 de enero de 2021 .
- ^ https://www.infoq.com/news/2019/11/rust-async-await/
- ^ "Introducción a los flujos de trabajo asincrónicos de F #" .
- ^ "Patrón asincrónico basado en tareas" . Microsoft . Consultado el 28 de septiembre de 2020 .
- ^ a b Stephen Cleary, Async / Await - Mejores prácticas en programación asincrónica
- ^ "aguardar - JavaScript (MDN)" . Consultado el 2 de mayo de 2017 .
- ^ "Guía de actualización de jQuery Core 3.0" . Consultado el 2 de mayo de 2017 .
- ^ "Domando a la bestia asincrónica con ES7" . Consultado el 12 de noviembre de 2015 .
- ^ Fundación, Node.js. "Nodo v8.0.0 (actual) - Node.js" . Node.js .
- ^ "El Comité ISO C ++ anuncia que el diseño de C ++ 20 ya tiene todas las funciones" .
- ^ "Votos de subvención de septiembre de 2018 - La Fundación Perl" . news.perlfoundation.org . Consultado el 26 de marzo de 2019 .
- ^ Matsakis, Niko. "¡Async-await en el establo Rust!" . Blog de óxido . Consultado el 7 de noviembre de 2019 .
- ^ Oppermann, Philipp. "Async / Await" . Consultado el 28 de octubre de 2020 .
- ^ Liebre 'No Bugs'. Ocho formas de manejar devoluciones sin bloqueo en programas de paso de mensajes CPPCON, 2018