stringtranslate.com

Asíncrono/espera

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]

Historia

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 , asyncy 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 asyncpalabra clave y el .awaitoperador 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 asyncy await. Esto se lanzó junto con una implementación concreta del modelo Actor con la actorpalabra clave [12] que usa async/await para mediar el acceso a cada actor desde el exterior.

Ejemplo C#

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 ; }                 

Una función que utiliza async/await puede utilizar tantas awaitexpresiones 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 Taskque 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 awaitesperar en el cuerpo ninguna tarea asincrónica, tampoco necesita el asyncmodificador 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 awaitresuelva 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 awaity, 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 ;                

Implementaciones

En fa sostenido

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 asyncen 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 }                                    

Cª#

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 voidestá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 Tasken su lugar, ya que permite un manejo de excepciones más intuitivo. [18]

Los métodos que hacen uso de awaitdeben declararse con la asyncpalabra clave. En los métodos que tienen un valor de retorno de tipo Task<T>, los métodos declarados con asyncdeben tener una declaración de retorno de tipo asignable a Ten lugar de Task<T>; el compilador envuelve el valor en el Task<T>genérico. También es posible utilizar awaitmétodos que tienen un tipo de retorno de Tasko 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 awaitpalabra 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 ; } 

En Python

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 ( ) )

En JavaScript

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++

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_awaitestá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_resumeen 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 ; }         


En Perl 5

El módulo Future::AsyncAwait [28] fue objeto de una subvención de la Fundación Perl en septiembre de 2018. [29]

En óxido

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 ); } 

En rápido

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 ) }

Beneficios y críticas

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. [ dudosodiscutir ]

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]

Véase también

Referencias

  1. ^ abcdefg Skeet, Jon (23 de marzo de 2019). C# en profundidad . Manning. ISBN 978-1617294532.
  2. ^ "Anuncio de Rust 1.39.0" . Consultado el 7 de noviembre de 2019 .
  3. ^ "Versión 0.9.4 lanzada - Blog de Nim" . Consultado el 19 de enero de 2020 .
  4. ^ "Concurrencia: el lenguaje de programación Swift (Swift 5.5)". docs.swift.org . Consultado el 28 de septiembre de 2021 .
  5. ^ Syme, Don; Petricek, Tomas; Lomov, Dmitry (2011). "El modelo de programación asincrónica de F#". Aspectos prácticos de los lenguajes declarativos . Apuntes de clase en informática. Vol. 6539. Springer Link. págs. 175–189. doi :10.1007/978-3-642-18378-2_15. ISBN . 978-3-642-18377-5. Recuperado el 29 de abril de 2021 .
  6. ^ "La historia temprana de F#, HOPL IV". Biblioteca digital ACM . Consultado el 29 de abril de 2021 .
  7. ^ Hejlsberg, Anders. "Anders Hejlsberg: Introducción a Async: simplificación de la programación asincrónica". Canal 9 MSDN . Microsoft . Consultado el 5 de enero de 2021 .
  8. ^ "async: ejecuta operaciones IO de forma asincrónica y espera sus resultados". Hackage .
  9. ^ "Novedades de Python 3.5: documentación de Python 3.9.1". docs.python.org . Consultado el 5 de enero de 2021 .
  10. ^ Gaurav, Seth (30 de noviembre de 2015). "Anuncio de TypeScript 1.7". TypeScript . Microsoft . Consultado el 5 de enero de 2021 .
  11. ^ Matsakis, Niko. «Async-await en Rust estable! | Blog de Rust». blog.rust-lang.org . Blog de Rust . Consultado el 5 de enero de 2021 .
  12. ^ "Concurrencia: el lenguaje de programación Swift (Swift 5.6)".
  13. ^ abcde Albahari, José (2022). C# 10 en pocas palabras . O'Reilly. ISBN 978-1-098-12195-2.
  14. ^ "Introducción a los flujos de trabajo asincrónicos de F#". 10 de octubre de 2007.
  15. ^ "Patrón asincrónico basado en tareas". Microsoft . Consultado el 28 de septiembre de 2020 .
  16. ^ Price, Mark J. (2022). C# 8.0 y .NET Core 3.0: desarrollo multiplataforma moderno: cree aplicaciones con C#, .NET Core, Entity Framework Core, ASP.NET Core y ML.NET con Visual Studio Code . ISBN 978-1-098-12195-2.
  17. ^ Tepliakov, Sergey (11 de enero de 2018). "Extensión de los métodos asincrónicos en C#". Soporte para desarrolladores . Consultado el 30 de octubre de 2022 .
  18. ^ Stephen Cleary, Async/Await: mejores prácticas en programación asincrónica
  19. ^ "Lanzamiento de Python 3.5.0".
  20. ^ "PEP 492 – Corrutinas con sintaxis async y await".
  21. ^ "await - JavaScript (MDN)" . Consultado el 2 de mayo de 2017 .
  22. ^ "Guía de actualización de jQuery Core 3.0" . Consultado el 2 de mayo de 2017 .
  23. ^ "Domando a la bestia asincrónica con ES7" . Consultado el 12 de noviembre de 2015 .
  24. ^ Fundación, Node.js (30 de mayo de 2017). "Node v8.0.0 (actual) - Node.js". Node.js .
  25. ^ "El Comité ISO C++ anuncia que el diseño de C++20 ya cuenta con todas sus características". 25 de febrero de 2019.
  26. ^ "Corrutinas (C++20)".
  27. ^ "s_task - biblioteca de corrutinas con espera para C". GitHub .
  28. ^ "Future::AsyncAwait - sintaxis de subrutina diferida para futuros".
  29. ^ "Votos de subvenciones de septiembre de 2018 - The Perl Foundation". news.perlfoundation.org . Consultado el 26 de marzo de 2019 .
  30. ^ Matsakis, Niko. "Async-await en Rust estable". Blog de Rust . Consultado el 7 de noviembre de 2019 .
  31. ^ Oppermann, Philipp. "Async/Await" . Consultado el 28 de octubre de 2020 .
  32. ^ "Copia archivada". Archivado desde el original el 23 de enero de 2022. Consultado el 20 de diciembre de 2021 .{{cite web}}: CS1 maint: copia archivada como título ( enlace )
  33. ^ "SE-0296". GitHub .
  34. ^ "Async Parte 3: Cómo el compilador de C# implementa funciones asíncronas".
  35. ^ 'No Bugs' Hare. Ocho formas de manejar retornos no bloqueantes en programas de paso de mensajes CPPCON, 2018
  36. ^ "¿De qué color es tu función?"
  37. ^ "Hilos virtuales".