Programación asíncrona de Rust async/await
En la programación moderna, la programación asíncrona es cada vez más importante, ya que permite que los programas no se bloqueen mientras esperan operaciones de E/S (como lectura/escritura de archivos, comunicación de red, etc.), mejorando así el rendimiento y la capacidad de respuesta.
La programación asíncrona es una forma de manejar operaciones no bloqueantes en Rust, que permite a los programas no bloquearse durante operaciones de E/S prolongadas, sino ejecutar otras tareas mientras esperan.
Rust proporciona diversas herramientas y bibliotecas para implementar la programación asíncrona, incluyendoasyncyawaitla palabra clave,futuresy tiempos de ejecución asíncronos (como tokio, async-std, etc.), además de otras herramientas auxiliares.
-
Future: Future es la abstracción en Rust que representa una operación asíncrona. Es un cálculo que quizás aún no ha terminado y que en algún momento futuro devolverá un valor o un error.
-
async/await:
asyncLa palabra clave se utiliza para definir una función asíncrona que devuelve un Future.awaitLa palabra clave se utiliza para pausar la ejecución del Future actual hasta que se complete.
Ejemplo
El siguiente ejemplo muestra cómo usar las palabras clave async y await para escribir una función asíncrona, y cómo ejecutar una tarea asíncrona dentro de una función asíncrona y esperar a que se complete.
Ejemplo
use tokio;
use tokio::time::{self, Duration};
// Función asíncrona que simula una tarea asíncrona
async fn async_task() -> u32 {
// Simular una operación asíncrona, esperar 1 segundo
time::sleep(Duration::from_secs(1)).await;
// Devolver el resultado
42
}
// Función de ejecución de tareas asíncronas
async fn execute_async_task() {
// Llamar a la tarea asíncrona y esperar a que se complete
let result = async_task().await;
// Mostrar el resultado
println!("Async task result: {}", result);
}
// Función principal
#[tokio::main]
async fn main() {
println!("Start executing async task...");
// Llamar a la función de ejecución de tareas asíncronas y esperar a que se complete
execute_async_task().await;
println!("Async task completed!");
}
En el código anterior, primero definimos una función asíncronaasync_task(), que simula una operación asíncrona, utilizandotokio::time::delay_for()para esperar 1 segundo y luego devolver el resultado 42. A continuación, definimos una función de ejecución de tareas asíncronasexecute_async_task(), en la que llamamos a la función asíncrona y usamosawaitpara esperar la finalización de la tarea asíncrona. Finalmente, en lamainusamos la funcióntokio::mainpara ejecutar la función de ejecución de tareas asíncronas y esperar a que se complete.
Al ejecutar el programa, se puede ver que el programa muestra un mensaje indicando que se inicia la ejecución de la tarea asíncrona, luego, tras esperar 1 segundo, muestra el resultado de la tarea asíncrona y, finalmente, muestra un mensaje de que la tarea asíncrona ha finalizado:
Start executing async task... Async task result: 42 Async task completed!
Este ejemplo demuestra el uso en Rust deasyncyawaitla palabra clave para escribir funciones asíncronas, así como cómo ejecutar tareas asíncronas dentro de una función asíncrona y esperar a que se completen.
El siguiente ejemplo utiliza la biblioteca tokio para realizar una solicitud HTTP asíncrona y mostrar el resultado de la respuesta:
Ejemplo 2
use std::error::Error;
use tokio::runtime::Runtime;
use reqwest::get;
// Función asíncrona para realizar una solicitud HTTP GET y devolver el resultado de la respuesta
async fn fetch_url(url: &str) -> Result<String, Box<dyn Error>> {
// Usar reqwest para realizar una solicitud HTTP GET asíncrona
let response = get(url).await?;
let body = response.text().await?;
Ok(body)
}
// Función de ejecución de tareas asíncronas
async fn execute_async_task() -> Result<(), Box<dyn Error>> {
// Realizar una solicitud HTTP asíncrona
let url = "https://jsonplaceholder.typicode.com/posts/1";
let result = fetch_url(url).await?;
// Mostrar el resultado de la respuesta
println!("Response: {}", result);
Ok(())
}
// Función principal
fn main() {
// Crear el tiempo de ejecución asíncrono
let rt = Runtime::new().unwrap();
// Ejecutar la tarea asíncrona en el tiempo de ejecución asíncrono
let result = rt.block_on(execute_async_task());
// Manejar el resultado de la ejecución de la tarea asíncrona
match result {
Ok(_) => println!("Async task executed successfully!"),
Err(e) => eprintln!("Error: {}", e),
}
}
En el código anterior, primero importamos las bibliotecas tokio y reqwest, utilizadas respectivamente para ejecutar tareas asíncronas y realizar solicitudes HTTP. Luego definimos una función asíncrona fetch_url para realizar una solicitud HTTP GET asíncrona y devolver el resultado de la respuesta.
A continuación, definimos una función de ejecución de tareas asíncronas execute_async_task, la cual realiza una solicitud HTTP asíncrona y muestra el resultado de la respuesta.
Finalmente, en la función main creamos un tiempo de ejecución asíncrono de tokio, ejecutamos la tarea asíncrona en él y procesamos el resultado de la ejecución de la tarea asíncrona.
Al ejecutar el programa, se puede ver que se muestra el resultado de la respuesta de la solicitud HTTP asíncrona. En el ejemplo se solicitaron los datos de una publicación de JSONPlaceholder y se imprimió su contenido.
Explicación de la programación asíncrona
La palabra clave async
La palabra clave async se utiliza para definir funciones asíncronas, es decir, funciones que devuelven un tipo Future o impl Future. Cuando se ejecuta una función asíncrona, esta devuelve un objeto Future incompleto, que representa un cálculo u operación que aún no ha finalizado.
Las funciones asíncronas pueden contener expresiones await para esperar la finalización de otras operaciones asíncronas.
Ejemplo
"Hello, world!".to_string()
}
La palabra clave await
La palabra clave await se utiliza para esperar la finalización de una operación asíncrona y obtener su resultado.
La expresión await solo se puede usar en funciones asíncronas o bloques asíncronos; pausa la ejecución de la función asíncrona actual, espera a que el Future esperado se complete y luego continúa con el código posterior.
Ejemplo
let result = hello().await;
println!("{}", result);
}
Valor de retorno de las funciones asíncronas
El tipo de retorno de una función asíncrona generalmente esimpl Future<Output = T>, dondeTes el tipo de resultado de la operación asíncrona. Debido a que el valor de retorno de una función asíncrona es un Future, se puede usar.awaitpara esperar la finalización de la operación asíncrona y obtener su resultado.
Ejemplo
a + b
}
Bloques asíncronos
Además de definir funciones asíncronas, Rust también proporciona la sintaxis de bloques asíncronos, lo que permite usar operaciones asíncronas en código síncrono. Un bloque asíncrono está formado porasync { }, y puede contener llamadas a funciones asíncronas yawaitexpresiones.
Ejemplo
let result1 = hello().await;
let result2 = add(1, 2).await;
println!("Result: {}, {}", result1, result2);
};
Ejecución de tareas asíncronas
En Rust, las tareas asíncronas generalmente necesitan ejecutarse en un contexto de ejecución; se pueden usartokio::main、async-stddetask::block_onofutures::executor::block_onfunciones como estas para ejecutar tareas asíncronas. Estas funciones aceptan una función asíncrona o un bloque asíncrono y lo ejecutan en el hilo actual o en el entorno de ejecución.
Ejemplo
fn main() {
task::block_on(print_hello());
}
Manejo de errores
awaitseguido de un?operador puede propagar errores. Siawaitel Future de ... devuelve un error al completarse, entonces ese error se propagará a quien lo invocó.
Ejemplo
some_async_operation().await?;
// Si some_async_operation falla, el error se propaga
}
Métodos de trait asíncronos
Rust permite definir métodos asíncronos para traits. Esto te permite definir operaciones asíncronas para objetos de diferentes tipos.
Ejemplo
async fn async_method(&self) -> Result<(), MyError>;
}
impl MyAsyncTrait for MyType {
async fn async_method(&self) -> Result<(), MyError> {
// Lógica asíncrona
}
}
Contexto asíncrono
En Rust, el código asíncrono suele ejecutarse en un tiempo de ejecución asíncrono (como Tokio o async-std). Estos tiempos de ejecución proporcionan mecanismos para programar y ejecutar tareas asíncronas.
Ejemplo
async fn main() {
some_async_operation().await;
}
En el código anterior,#[tokio::main]el macro de atributomainenvuelve la función en un tiempo de ejecución asíncrono.
Macros asíncronas
Rust proporciona algunas macros asíncronas, comotokio::spawn, que se utilizan para iniciar nuevas tareas asíncronas en el tiempo de ejecución asíncrono.
Ejemplo
async fn main() {
let handle = tokio::spawn(async {
// Lógica asíncrona
});
handle.await.unwrap();
}
E/S asíncrona
La biblioteca estándar de Rust proporciona operaciones de E/S asíncronas, comotokio::fs::Fileyasync_std::fs::File。
Ejemplo
use tokio::io::{self, AsyncReadExt};
#[tokio::main]
async fn main() -> io::Result<()> {
let mut file = File::open("file.txt").await?;
let mut contents = String::new();
file.read_to_string(&mut contents).await?;
println!("Contents: {}", contents);
Ok(())
}
Canales asíncronos
Algunos tiempos de ejecución asíncronos de Rust ofrecen canales asíncronos (como tokio::sync::mpsc), que permiten enviar mensajes entre tareas asíncronas.
Ejemplo
use tokio::spawn;
#[tokio::main]
async fn main() {
let (tx, mut rx) = mpsc::channel(32);
let child = spawn(async move {
let response = "Hello, world!".to_string();
tx.send(response).await.unwrap();
});
let response = rx.recv().await.unwrap();
println!("Received: {}", response);
child.await.unwrap();
}
Resumen
El modelo de programación asíncrona async/await de Rust ofrece una forma concisa y eficiente de manejar operaciones asíncronas.
Permite a los desarrolladores manejar operaciones asíncronas de una manera más natural e intuitiva, manteniendo al mismo tiempo la seguridad y el rendimiento de Rust.
Con async/await, Rust ofrece soporte de primera clase para la programación asíncrona, lo que facilita la escritura de programas asíncronos eficientes y legibles.
Otras extensiones