async/await
Rust 的 async/await 是异步编程的语法糖,它使异步代码像同步代码一样易读和编写。async/await 构建在 Future trait 之上,允许开发者定义异步函数,并使用 await 来暂停执行直到 Future 就绪。 与同步代码不同,async/await 非阻塞线程,而是通过运行时调度任务,提高 I/O 密集应用的效率。 async/await 自 Rust 1.39 起稳定,是 Rust 异步生态的核心。
1. Rust async/await 简介
1.1 定义和目的
async/await 是 Rust 异步编程的语法糖:
- async fn:定义异步函数,返回一个 Future。
- await:暂停当前任务,等待 Future 完成,返回其 Output。
- 目的:使异步代码更易读,像同步一样书写,而无需手动 poll Future。 它解决异步回调地狱,提供线性代码流。 async/await 基于 generator 实现,每个 await 点是潜在暂停点。
1.2 与同步编程的区别
- 同步:顺序执行,I/O 阻塞。
- 异步:非阻塞,await 时切换任务。
- 运行时:async 需要 executor 如 Tokio 执行。
- 错误:async 用 ? 传播 Result
。
何时选择 async/await? I/O 密集任务,如 web 服务;同步适合 CPU 密集。
2. 基础语法
2.1 async fn 和 await
async fn 返回 impl Future:
- 例子1: 简单 async fn
async fn hello() -> String { "hello".to_string() } #[tokio::main] async fn main() { println!("{}", hello().await); }
-
await 等待完成。
-
例子2: async 块
#[tokio::main] async fn main() { let result = async { 42 }.await; println!("{}", result); }
- 匿名 async。
2.2 与 Future 的关系
async 是 Future 语法糖:
- 例子3: 手动 poll vs await
use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll, Waker}; struct ManualFut; impl Future for ManualFut { type Output = i32; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { Poll::Ready(42) } } #[tokio::main] async fn main() { let fut = ManualFut; println!("{}", fut.await); }
- await 内部 poll。
3. 运行时集成
async 需要运行时执行。
3.1 Tokio 运行时
- 例子4: Tokio main
#[tokio::main] async fn main() { println!("Hello Tokio"); }
-
属性启动 runtime。
-
例子5: 自定义 runtime
use tokio::runtime::Runtime; fn main() { let rt = Runtime::new().unwrap(); rt.block_on(async { println!("Hello"); }); }
- 手动 runtime。
3.2 async-std 运行时
- 例子6: async-std main
use async_std::task; fn main() { task::block_on(async { println!("Hello async-std"); }); }
- 替代 runtime。
4. 并发和同步原语
4.1 Spawn 任务
- 例子7: Spawn
#[tokio::main] async fn main() { let handle = tokio::spawn(async { "spawned" }); println!("{}", handle.await.unwrap()); }
- 并发任务。
4.2 Channels
- 例子8: mpsc
use tokio::sync::mpsc; #[tokio::main] async fn main() { let (tx, mut rx) = mpsc::channel(32); tokio::spawn(async move { tx.send("hello").await.unwrap(); }); println!("{}", rx.recv().await.unwrap()); }
- 通信。
4.3 Mutex
- 例子9: Mutex
use tokio::sync::Mutex; use std::sync::Arc; #[tokio::main] async fn main() { let m = Arc::new(Mutex::new(0)); let m2 = m.clone(); tokio::spawn(async move { *m2.lock().await += 1; }); *m.lock().await += 1; println!("{}", *m.lock().await); }
- 共享状态。
5. 错误处理
5.1 Result in async
- 例子10: ? in async
async fn fetch() -> Result<String, anyhow::Error> { Ok("data".to_string()) } #[tokio::main] async fn main() -> Result<(), anyhow::Error> { let data = fetch().await?; println!("{}", data); Ok(()) }
- 传播错误。
5.2 anyhow
- 例子11: anyhow
use anyhow::{Result, anyhow}; async fn error_task() -> Result<()> { Err(anyhow!("error")) } #[tokio::main] async fn main() { if let Err(e) = error_task().await { println!("Error: {}", e); } }
- 简单错误。
6. Streams 和 Sinks
6.1 Stream
- 例子12: iter stream
use tokio_stream::StreamExt; #[tokio::main] async fn main() { let mut stream = tokio_stream::iter(vec![1, 2, 3]); while let Some(item) = stream.next().await { println!("{}", item); } }
- 异步 iter。
6.2 Sink
- 例子13: sink send
use futures::sink::SinkExt; use tokio::sync::mpsc; #[tokio::main] async fn main() { let (mut tx, rx) = mpsc::channel(32); tx.send("hello").await.unwrap(); drop(tx); // rx 消费 }
- 发送到 sink。
7. 高级主题
7.1 Async Traits
- 例子14: async trait
trait AsyncService { async fn handle(&self) -> String; } struct Service; impl AsyncService for Service { async fn handle(&self) -> String { "handled".to_string() } } #[tokio::main] async fn main() { let s = Service; println!("{}", s.handle().await); }
- 异步 trait。
7.2 Custom Await
- 例子15: await chain
#![allow(unused)] fn main() { async fn chain() { let data = fetch().await.unwrap(); process(data).await; } }
- 链式 await。
8. 常见用例
- Web 客户端:fetch URL。
- 服务器:处理请求。
- 数据库:异步查询。
- 定时任务:delay。
- 并发 I/O:多 fetch。
9. 最佳实践
- 用 Tokio:生产运行时。
- Pinning 处理:!Unpin 用 Box::pin。
- 错误统一:anyhow/thiserror。
- 测试:tokio::test。
- 文档:lifetime 和 Send。
- 性能:避免阻塞操作。
10. 常见陷阱和错误
- 阻塞:sync in async 阻塞 runtime;用 async。
- Lifetime:借用需 'static。
- Pinning:!Unpin 需 Pin。
- 无 runtime:async 不执行;用 main。
- 取消:drop Future 取消;处理 cleanup。