En programación informática , el patrón async/await es una característica sintáctica de muchos lenguajes de programación que permite que una función asincrónica y no bloqueante se estructure de manera similar a una función sincrónica ordinaria. Está semánticamente relacionado con el concepto de corrutina y a menudo se implementa utilizando técnicas similares, y está destinado principalmente a 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 característica se encuentra en C# , [1] : 10 C++ , Python , F# , Hack , Julia , Dart , Kotlin , Rust , [2] Nim , [3] JavaScript y Swift . [4]
F# agregó flujos de trabajo asincrónicos con puntos de espera en la versión 2.0 en 2007. [5] Esto influyó en el mecanismo async/await agregado a C#. [6]
Microsoft lanzó por primera vez una versión de C# con async/await en Async CTP (2011). Más tarde se lanzó oficialmente en C# 5 (2012). [7] [1] : 10
El desarrollador principal de Haskell, Simon Marlow, creó el paquete async en 2012. [8]
Python agregó soporte para async/await con la versión 3.5 en 2015 [9] agregando 2 nuevas palabras clave , async
y await
.
TypeScript agregó soporte para async/await con la versión 1.7 en 2015. [10]
Javascript agregó soporte para async/await en 2017 como parte de la edición JavaScript de ECMAScript 2017.
Rust agregó soporte para async/await con la versión 1.39.0 en 2019 usando la async
palabra clave y el .await
operador postfix, ambos introducidos en la edición 2018 del lenguaje. [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
.
Swift agregó compatibilidad con async/await con la versión 5.5 en 2021, agregando 2 nuevas palabras clave async
y await
. Esto se lanzó junto con una implementación concreta del modelo Actor con la actor
palabra clave [12] que usa async/await para mediar el acceso a cada actor desde el exterior.
La función C# a continuación, que descarga un recurso desde una URI y devuelve la longitud del recurso, utiliza este patrón async/await:
public async Task < int > FindSizeOfPageAsync ( Uri uri ) { var cliente = new HttpClient (); byte [] datos = await cliente . GetByteArrayAsync ( uri ); devolver datos . Longitud ; }
async
palabra clave indica a C# que el método es asincrónico, lo que significa que puede usar una cantidad arbitraria de await
expresiones y vinculará el resultado a una promesa . [1] : 165–168 Task<T>
, es el análogo de C# al concepto de promesa, y aquí se indica que tiene un valor de resultado de tipo int
.new HttpClient().GetByteArrayAsync(uri)
, [13] : 189–190, 344 [1] : 882 que es otro método asincrónico que devuelve un Task<byte[]>
. 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 no bloqueante (como un hilo en segundo plano ) y devolverá inmediatamente un no resuelto, no rechazado Task<byte[]>
a esta función.await
palabra clave adjunta a Task
, esta función procederá inmediatamente a devolver un Task<int>
a su llamador, quien luego podrá continuar con otro procesamiento según sea necesario.GetByteArrayAsync()
finalizada la descarga, resolverá el Task
problema que devolvió con los datos descargados. Esto activará una devolución de llamada y hará FindPageSizeAsync()
que continúe la ejecución asignando ese valor a data
.data.Length
, un entero simple que indica la longitud de la matriz. El compilador reinterpreta esto como la resolución del valor Task
devuelto anteriormente, lo que activa una devolución de llamada en el llamador del método para hacer algo con ese valor de longitud.Una función que utiliza async/await puede utilizar tantas await
expresiones como desee, y cada una se manejará de la misma manera (aunque una promesa solo se devolverá al llamador para el primer await, mientras que cada otro await utilizará devoluciones de llamadas 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 permiten al programa esperar múltiples promesas a la vez o en algún patrón especial (como C# Task.WhenAll()
, [1] : 174–175 [13] : 664–665 que devuelve un valor sin Task
que se resuelva cuando se hayan resuelto todas las tareas en los argumentos). Muchos tipos de promesa también tienen características adicionales más allá de lo que normalmente utiliza 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 especialmente de larga duración.
En el caso particular de C#, y en muchos otros lenguajes con esta característica, el patrón async/await no es una parte fundamental del entorno 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 bytecode IL :
public Task < int > FindSizeOfPageAsync ( Uri uri ) { var cliente = new HttpClient (); Tarea < byte [] > dataTask = cliente . GetByteArrayAsync ( uri ); Tarea < int > afterDataTask = dataTask . ContinueWith (( originalTask ) => { return originalTask . Result . Length ; }); return afterDataTask ; }
Por este motivo, si un método de interfaz necesita devolver un objeto de promesa, pero no necesita await
esperar en el cuerpo 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 podría proporcionar una promesa que se resuelva inmediatamente en algún valor de resultado (como Task.FromResult()
[13] : 656 de C# ), o simplemente puede devolver la promesa de otro método que sea exactamente la promesa necesaria (como cuando se posterga 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, en realidad no es bloqueante y potencialmente multiproceso, lo que significa que pueden ocurrir muchos eventos intermedios mientras se espera que se await
resuelva la promesa a la que apunta un . Por ejemplo, el siguiente código, si bien siempre tiene éxito en un modelo de bloqueo sin await
, puede experimentar eventos intermedios durante el await
y, por lo tanto, puede encontrar que el estado compartido cambia desde debajo de él:
var a = state.a ; var client = new HttpClient (); var data = await client.GetByteArrayAsync ( uri ) ; Debug.Assert ( a == state.a ) ; // Posible fallo, ya que el valor de state.a puede haber sido cambiado // por el controlador del evento potencialmente interviniente. return data.length ;
En 2007, F# agregó flujos de trabajo asincrónicos con la versión 2.0. [14] 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# agregan un signo de exclamación (!) a las palabras clave para iniciar tareas asincrónicas.
La siguiente función asincrónica descarga datos de una URL mediante un flujo de trabajo asincrónico:
deje que asyncSumPageSizes ( uris : #seq <Uri> ) : Async <int> = async { use httpClient = new HttpClient ( ) deje que ! pages = uris | > Seq.map ( httpClient.GetStringAsync >> Async.AwaitTask ) | > Async.Parallel devuelva páginas | > Seq.fold ( fun acumulador actual - > actual.Longitud + acumulador ) 0 }
En 2012, C# agregó el patrón async/await en C# con la versión 5.0, al que Microsoft se refiere como el patrón asincrónico basado en tareas (TAP). [15] Los métodos asincrónicos generalmente devuelven void
, Task
, Task<T>
, [13] : 35 [16] : 546–547 [1] : 22, 182 ValueTask
o ValueTask<T>
. [13] : 651–652 [1] : 182–184 El código de usuario puede definir tipos personalizados que los métodos asincrónicos pueden devolver a través de generadores de métodos asincrónicos personalizados , pero este es un escenario avanzado y poco común. [17] Los métodos asincrónicos que devuelven void
están destinados a controladores de eventos ; en la mayoría de los casos en los que un método sincrónico devolvería void
, se recomienda devolver Task
en su lugar, ya que permite un manejo de excepciones más intuitivo. [18]
Los métodos que hacen uso de await
deben declararse con la async
palabra clave. En los métodos que tienen un valor de retorno de tipo Task<T>
, los métodos declarados con async
deben tener una declaración de retorno de tipo asignable a T
en lugar de Task<T>
; el compilador envuelve el valor en el Task<T>
genérico. También es posible utilizar await
métodos que tienen un tipo de retorno de Task
o Task<T>
que se declaran sin async
.
El siguiente método asincrónico descarga datos de una URL mediante await
. Debido a que este método emite una tarea para cada URI antes de requerir la finalización con la await
palabra clave, los recursos se pueden cargar al mismo tiempo en lugar de esperar a que finalice el último recurso antes de comenzar a cargar el siguiente.
público async Tarea < int > SumPageSizesAsync ( IEnumerable < Uri > uris ) { var cliente = new HttpClient (); int total = 0 ; var loadUriTasks = new Lista < Tarea < byte [] >> (); foreach ( var uri en uris ) { var loadUriTask = cliente.GetByteArrayAsync ( uri ) ; loadUriTasks.Add ( loadUriTask ) ; } foreach ( var loadUriTask en loadUriTasks ) { statusText.Text = $"Se encontraron { total } bytes... " ; var resourceAsBytes = await loadUriTask ; total + = resourceAsBytes.Longitud ; } statusText . Text = $"Se encontraron {total} bytes en total" ; devolver total ; }
Python 3.5 (2015) [19] agregó soporte para async/await como se describe en PEP 492 (escrito e implementado por Yury Selivanov). [20]
importar asyncioasync def main (): print ( " hola" ) await asyncio.sleep ( 1 ) print ( " mundo" ) asyncio .run ( principal ( ) )
El operador await en JavaScript solo se puede utilizar desde dentro de una función async o en el nivel superior de un módulo . Si el parámetro es una promesa , la ejecución de la función async se reanudará cuando se resuelva la promesa (a menos que la promesa sea rechazada, en cuyo caso se generará un error que se puede manejar con el manejo de excepciones normal de JavaScript ). Si el parámetro no es una promesa, el parámetro en sí se devolverá inmediatamente. [21]
Muchas bibliotecas proporcionan objetos de promesa que también se pueden usar con await, siempre que coincidan con la especificación de promesas nativas de JavaScript. Sin embargo, las promesas de la biblioteca jQuery no eran compatibles con Promises/A+ hasta jQuery 3.0. [22]
He aquí un ejemplo (modificado de este artículo [23] ):
función asíncrona createNewDoc () { let response = await db.post ( { }); // publicar un nuevo documento return db.get ( response.id ); // buscar por id } función asíncrona principal ( ) { try { let doc = await createNewDoc ( ); console.log ( doc ); } catch ( err ) { console.log ( err ); } } principal ( ) ;
La versión 8 de Node.js incluye una utilidad que permite utilizar los métodos basados en devoluciones de llamadas de la biblioteca estándar como promesas. [24]
En C++, await (llamado co_await en C++) se ha fusionado oficialmente en la versión 20. [ 25] El soporte para este, las corrutinas y las palabras clave como co_await
están disponibles en los compiladores GCC y MSVC , mientras que Clang tiene soporte parcial.
Vale la pena señalar que std::promise y std::future, aunque parecerían ser objetos que se pueden esperar, no implementan ninguno de los mecanismos necesarios para que las corrutinas los devuelvan y se los espere utilizando co_await. Los programadores deben implementar una serie de funciones miembro públicas, como await_ready
, await_suspend
, y await_resume
en el tipo de retorno para que se espere el tipo. Se pueden encontrar detalles en cppreference. [26]
#include <iostream> #include "TareaAguardablePersonalizada.h" utilizando el espacio de nombres std ; TareaAguardablePersonalizada < int > agregar ( int a , int b ) { int c = a + b ; co_return c ; } CustomAwaitableTask < int > prueba () { int ret = co_await add ( 1 , 2 ); cout << "return " << ret << endl ; co_return ret ; } int main () { auto tarea = prueba (); devuelve 0 ; }
El lenguaje C no admite await/async. Algunas bibliotecas de corrutinas como s_task [27] simulan las palabras clave await/async con macros.
#include <stdio.h> #include "s_task.h" // define la memoria de pila para las 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, segundos de retraso = %d, i = %d \n " , n , n , i ); s_task_msleep ( __await__ , n * 1000 ); //s_task_yield(__await__); } } vacío tarea_principal ( __async__ , vacío * 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_task , ( void * ) 2 ); para ( i = 0 ; i < 4 ; ++ i ) { printf ( "tarea_principal arg = %p, i = %d \n " , arg , i ); s_task_yield ( __await__ ); } // esperar a que salgan las subtareas s_task_join ( __await__ , g_stack0 ); s_task_join ( __await__ , g_stack1 ); } int principal ( int argc , char * argv ) { sistema_de_inicio_de_tareas (); //crear 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 ( "todas las tareas han terminado \n " ); return 0 ; }
El módulo Future::AsyncAwait [28] fue objeto de una subvención de la Fundación Perl en septiembre de 2018. [29]
El 7 de noviembre de 2019, se lanzó async/await en la versión estable de Rust. [30] Las funciones async en Rust se convierten en funciones simples que devuelven valores que implementan el atributo Future. Actualmente, se implementan con una máquina de estados finitos . [31]
// En el Cargo.toml del cajón, necesitamos `futures = "0.3.0"` en la sección de dependencias, // para que podamos usar el cajón de futurosextern crate futures ; // No hay ningún ejecutor actualmente en la biblioteca `std`. // Esto se reduce a algo como // `fn async_add_one(num: u32) -> impl Future<Output = u32>` async fn async_add_one ( num : u32 ) -> u32 { num + 1 } async fn ejemplo_tarea () { let numero = async_add_one ( 5 ) .await ; println! ( "5 + 1 = {}" , numero ); } fn main () { // La creación del futuro no inicia la ejecución. let future = example_task (); // El `Futuro` solo se ejecuta cuando realmente lo sondeamos, a diferencia de Javascript. futures :: executor :: block_on ( future ); }
Swift 5.5 (2021) [32] agregó soporte para async/await como se describe en SE-0296. [33]
func getNumber ( ) async lanza - > Int { try await Task.sleep ( nanosegundos : 1_000_000_000 ) return 42 } Tarea { deje que primero = intente esperar obtenerNumero () deje que segundo = intente esperar obtenerNumero () imprima ( primero + segundo ) }
El patrón async/await es especialmente atractivo para los diseñadores de lenguajes que no tienen o controlan su propio entorno de ejecución, ya que async/await se puede implementar únicamente como una transformación a una máquina de estados en el compilador. [34]
Los partidarios afirman que se puede escribir código asincrónico y no bloqueante con async/await que se parece casi al código sincrónico y bloqueante tradicional. En particular, se ha argumentado que await es la mejor manera de escribir código asincrónico en programas de paso de mensajes ; en particular, se citaron como ventajas de await la proximidad al código bloqueante, la legibilidad y la cantidad mínima de código repetitivo . [35] Como resultado, async/await hace que a la mayoría de los programadores les resulte más fácil razonar sobre sus programas, y await tiende a promover un código no bloqueante mejor y más robusto en las aplicaciones que lo requieren. [ dudoso – discutir ]
Los críticos de async/await señalan que el patrón tiende a provocar que el código circundante también sea asincrónico; y que su naturaleza contagiosa divide los ecosistemas de bibliotecas de los lenguajes entre bibliotecas y API sincrónicas y asincrónicas, un problema al que a menudo se hace referencia como "coloración de funciones". [36] Las alternativas a async/await que no sufren este problema se denominan "sin color". Algunos ejemplos de diseños sin color incluyen las goroutines de Go y los hilos virtuales de Java . [37]
{{cite web}}
: CS1 maint: copia archivada como título ( enlace )