En programación de computadoras , el patrón async/await es una característica sintáctica de muchos lenguajes de programación que permite estructurar una función asincrónica y sin bloqueo de una manera similar a una función síncrona ordinaria. Está semánticamente relacionado con el concepto de 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 duració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 , Swift [4] y Zig . [5]
F# agregó flujos de trabajo asíncronos con puntos de espera en la versión 2.0 en 2007. [6] Esto influyó en el mecanismo asíncrono/en espera agregado a C#. [7]
Microsoft lanzó por primera vez una versión de C# con async/await en Async CTP (2011). Posteriormente se lanzó oficialmente en C# 5 (2012). [8] [1] : 10
El desarrollador principal de Haskell, Simon Marlow, creó el paquete asíncrono en 2012. [9]
Python agregó soporte para async/await con la versión 3.5 en 2015 [10] agregando 2 nuevas palabrasasync
clave y await
.
TypeScript agregó soporte para async/await con la versión 1.7 en 2015. [11]
JavaScript agregó soporte para async/await en 2017 como parte de la edición JavaScript 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. [12]
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ó soporte para async/await con la versión 5.5 en 2021, agregando 2 nuevas palabras clave async
y await
. Esto se publicó junto con una implementación concreta del modelo Actor con la actor
palabra clave [13] que utiliza async/await para mediar el acceso a cada actor desde el exterior.
La siguiente función de C# , que descarga un recurso desde un URI y devuelve la longitud del recurso, utiliza este patrón async/await:
Tarea asíncrona pública <int> FindPageSizeAsync ( Uri uri ) { var client = new HttpClient ( ) ; byte [] datos = espera cliente . GetByteArrayAsync ( uri ); datos de retorno . 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)
, [14] : 189–190, 344 [1] : 882, que es otro método asincrónico que devuelve un archivo 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 sin bloqueo (como un hilo en segundo plano ) e inmediatamente devolverá un archivo no resuelto y no rechazado Task<byte[]>
a esta función.await
palabra clave adjunta a Task
, esta función procederá inmediatamente a devolver a Task<int>
a su llamador, quien luego podrá continuar con otro procesamiento según sea necesario.GetByteArrayAsync()
finalice su descarga, resolverá el Task
retorno con los datos descargados. Esto activará una devolución de llamada y hará FindPageSizeAsync()
que la ejecución continúe asignando ese valor a data
.data.Length
, un número entero simple que indica la longitud de la matriz. El compilador reinterpreta esto como una resolución de Task
lo devuelto anteriormente, lo que activa una devolución de llamada en la persona que llama al método para hacer algo con ese valor de longitud.Una función que usa async/await puede usar tantas await
expresiones como quiera, y cada una se manejará de la misma manera (aunque solo se devolverá una promesa a la persona que llama para la primera espera, mientras que todas las demás esperas utilizarán devoluciones de llamada internas). Una función también puede contener un objeto de promesa directamente y realizar otros procesos 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 [14] : 664–665 que devuelve un valor sin valor Task
que se resuelve cuando todas de las tareas en los argumentos han sido resueltas). Muchos tipos de promesas también tienen características adicionales más allá de las que normalmente usa el patrón async/await, como poder configurar más de una devolución de llamada de resultados 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 ) { var cliente = nuevo HttpClient ( ) ; Tarea < byte [] > dataTask = cliente . GetByteArrayAsync ( uri ); Tarea <int> afterDataTask = dataTask . Continuar con (( tarea original ) => { retornar tarea original . Resultado . Longitud ; }); devolver después de la tarea de datos ; }
Debido a esto, si un método de interfaz necesita devolver un objeto de promesa, pero no requiere que await
el cuerpo espere 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 resuelve inmediatamente en algún valor de resultado (como C# Task.FromResult()
[14] : 656 ), o simplemente puede devolver la promesa de otro método que resulta ser la promesa exacta necesaria (como cuando aplazar 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 de bloqueo y es potencialmente multiproceso, lo que significa que pueden ocurrir muchos eventos intermedios mientras se espera que se await
resuelva la promesa dirigida por 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 ha cambiado desde debajo de él:
var a = estado . a ; cliente var = nuevo HttpClient (); var datos = esperar cliente . GetByteArrayAsync ( uri ); Depurar . Afirmar ( a == estado . a ); // Posible error, ya que el valor de state.a puede haber sido cambiado // por el controlador del evento potencialmente interventor. datos de retorno . Longitud ;
En 2007, F# agregó flujos de trabajo asincrónicos con la versión 2.0. [15] 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 un bang (!) 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 () let! páginas = uris |> Sec . mapa ( httpClient . GetStringAsync >> Async . AwaitTask ) |> Async . Páginas de retorno paralelas |> Sec . plegar ( acumulador divertido 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 patrón asíncrono basado en tareas (TAP). [16] Los métodos asíncronos generalmente devuelven void
, Task
, Task<T>
, [14] : 35 [17] : 546–547 [1] : 22, 182 ValueTask
o ValueTask<T>
. [14] : 651–652 [1] : 182–184 El código de usuario puede definir tipos personalizados que los métodos asíncronos pueden devolver a través de constructores de métodos asíncronos personalizados , pero este es un escenario avanzado y poco común. [18] Los métodos asíncronos que regresan 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
, Task
se recomienda devolver, ya que permite un manejo de excepciones más intuitivo. [19]
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<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 tengan un tipo de retorno de Task
o Task<T>
que se declaren sin él async
.
El siguiente método asíncrono descarga datos de una URL usando await
. Debido a que este método emite una tarea para cada uri antes de requerir su 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.
tarea asíncrona pública < int > SumPageSizesAsync ( IEnumerable < Uri > uris ) { var client = new HttpClient (); entero total = 0 ; var loadUriTasks = nueva Lista < Tarea < byte [] >> (); foreach ( var uri en uris ) { var loadUriTask = cliente . GetByteArrayAsync ( uri ); cargarUriTasks . Agregar ( loadUriTask ); } foreach ( var loadUriTask en loadUriTasks ) { statusText . Texto = $"Encontrados {total} bytes..." ; var recursoAsBytes = espera loadUriTask ; total += recursoAsBytes . Longitud ; } texto de estado . Texto = $"Encontrado {total} bytes en total" ; total de retorno ; }
Python 3.5 (2015) [20] ha agregado soporte para async/await como se describe en PEP 492 (escrito e implementado por Yury Selivanov). [21]
importar asincioasync def main (): print ( "hola" ) espera asyncio . dormir ( 1 ) imprimir ( "mundo" )asincio . ejecutar ( principal ())
El operador de espera en JavaScript sólo se puede utilizar desde dentro de una función asíncrona o en el nivel superior de un módulo . 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. [22]
Muchas bibliotecas proporcionan objetos de promesa que también se pueden usar con await, siempre que coincidan con la especificación de las promesas nativas de JavaScript. Sin embargo, las promesas de la biblioteca jQuery no eran compatibles con Promises/A+ hasta jQuery 3.0. [23]
Aquí hay un ejemplo (modificado de este artículo [24] ):
función asíncrona createNewDoc () { dejar respuesta = esperar db . correo ({}); // publicar un nuevo documento devolver db . obtener ( respuesta . id ); // buscar por id } función asíncrona principal () { intentar { dejar doc = esperar crearNuevoDoc (); consola . iniciar sesión ( doc ); } captura ( err ) { consola . iniciar sesión ( errar ); } } principal ();
Node.js versión 8 incluye una utilidad que permite utilizar los métodos basados en devolución de llamada de la biblioteca estándar como promesas. [25]
En C++, await (llamado co_await en C++) se ha fusionado oficialmente en la versión 20 . [26] El soporte para él, 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ía que serían objetos en espera, no implementan ninguna de las maquinarias necesarias para ser devueltos por las corrutinas y ser esperados usando co_await. Los programadores deben implementar una serie de funciones de miembros públicos, como await_ready
, await_suspend
y await_resume
en el tipo de retorno para que se espere el tipo. Los detalles se pueden encontrar en cppreference. [27]
#include <iostream> #include "CustomAwaitableTask.h" usando el espacio de nombres estándar ; CustomAwaitableTask < int > agregar ( int a , int b ) { int c = a + b ; co_retorno c ; } CustomAwaitableTask < int > prueba () { int ret = co_await add ( 1 , 2 ); cout << "retorno" << ret << endl ; co_return ret ; } int main () { tarea automática = prueba (); devolver 0 ; }
El lenguaje C no admite await/async. Algunas bibliotecas de rutinas como s_task [28] simulan las palabras clave await/async con macros.
#incluye <stdio.h> #incluye "s_task.h" // define la memoria de pila para tareas int g_stack_main [ 64 * 1024 / sizeof ( int )]; int g_stack0 [ 64 * 1024 / tamaño de ( int )]; int g_stack1 [ 64 * 1024 / tamaño de ( int )]; void sub_task ( __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__); } } void main_task ( __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 , tamaño de ( g_stack1 ), sub_tarea , ( void * ) 2 ); for ( i = 0 ; i < 4 ; ++ i ) { printf ( "task_main arg = %p, i = %d \n " , arg , i ); s_task_yield ( __await__ ); } // espera a que salgan las subtareas s_task_join ( __await__ , g_stack0 ); s_task_join ( __await__ , g_stack1 ); } int principal ( 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 " ); devolver 0 ; }
El módulo Future::AsyncAwait [29] fue objeto de una subvención de la Fundación Perl en septiembre de 2018. [30]
El 7 de noviembre de 2019, se lanzó async/await en la versión estable de Rust. [31] Las funciones asíncronas en Rust se convierten en funciones simples que devuelven valores que implementan el rasgo Futuro. Actualmente se implementan con una máquina de estados finitos . [32]
// 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 ; // Actualmente no hay ningún ejecutor en la biblioteca `std`. // Esto se desazucará 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 example_task () { let número = async_add_one ( 5 ). esperar ; imprimir! ( "5 + 1 = {}" , número ); } fn main () { // Crear el futuro no inicia la ejecución. let futuro = ejemplo_tarea (); // El `Futuro` solo se ejecuta cuando realmente lo sondeamos, a diferencia de Javascript. futuros :: ejecutor :: block_on ( futuro ); }
Swift 5.5 (2021) [33] agregó soporte para async/await como se describe en SE-0296. [34]
func getNumber () lanzamientos asíncronos -> Int { intenta esperar tarea . dormir ( nanosegundos : 1_000_000_000 ) regresar 42 } Tarea { dejar primero = intentar esperar obtener Número () dejar segundo = intentar esperar obtener Número () imprimir ( primero + segundo ) }
El patrón async/await es especialmente atractivo para los diseñadores de lenguajes que no tienen ni controlan su propio tiempo de ejecución, ya que async/await se puede implementar únicamente como una transformación a una máquina de estados en el compilador. [35]
Los partidarios afirman que se puede escribir código asincrónico y sin bloqueo con async/await que se parece casi al código de bloqueo síncrono 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 beneficios pendientes estar cerca del código de bloqueo, la legibilidad y la cantidad mínima de código repetitivo . [36] 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 sin bloqueo mejor y más robusto en las aplicaciones que lo requieren. [ dudoso ]
Los críticos de async/await señalan que el patrón tiende a hacer que el código circundante también sea asincrónico; y que su naturaleza contagiosa divide los ecosistemas de bibliotecas de los idiomas entre bibliotecas y API sincrónicas y asincrónicas, un problema a menudo denominado "coloración de funciones". [37] Las alternativas a async/await que no sufren este problema se denominan "incoloras". Ejemplos de diseños incoloros incluyen las rutinas de Go y los hilos virtuales de Java . [38]
{{cite web}}
: Mantenimiento CS1: copia archivada como título ( enlace )