3 分钟入门 Rust 异步

3/8/2025

Rust 异步简介

ust 中的异步操作是通过 Future trait 来描述的。Future 表示一个还未完成的计算,其核心方法是 poll。Rust 的异步运行时通过轮询(polling)来判断一个 Future 是否已经完成,轮询的本质是检查任务是否就绪,但并不会阻塞线程。

Rust 异步特点

  • 惰性(轮询时获取进展)
  • 零成本(无额外内存开销)
  • 提供标准,不提供内置运行时

异步组成

  • 标准库
  • 编辑器
  • trait Future: Future 是 Rust 异步编程的核心概念,代表一个可能还未完成的计算或任务。
  • async/await
    • async: 用来声明一个异步函数或块,这个函数或块会返回一个实现了 Future trait 的对象。
    • await: 用来暂停执行直到一个异步操作完成。当 Future 准备好时,await 继续执行后面的代

trait Future

pub trait Future {
    type Output;

    // Required method
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}

futures

futures 是 Rust 官方提供一个包。它的特点是:

特性/模块描述
组合子 (Combinators)提供用于简化异步操作链式调用的工具,允许对 Future 进行变换、组合和错误处理。
StreamExtStream 的扩展 trait,提供操作流的组合子,如 next()filter()map()for_each()
Sink代表一个异步写入器,允许异步发送数据到某个目标(如网络、文件等)。
futures::future::timeout允许为异步任务设置超时时间,确保不会无限等待任务完成。
组合与并发执行futures crate 提供了便捷的功能组合工具,允许多个异步任务并发执行。

执行器

Rust 中有很多执行器,其中就包含大名鼎鼎的的 Tokioasync_std

Rust 的异步编程使用执行器来运行异步任务。执行器管理任务队列,并不断轮询未完成的任务,直到它们完成。

  • 单线程与多线程执行器:执行器可以是单线程的,也可以是多线程的。单线程执行器一次只能轮询一个任务,而多线程执行器可以在多个线程中并发地轮询多个任务。

  • 任务的轮询与唤醒:执行器负责唤醒那些未完成但即将就绪的任务,让它们继续执行。

Tokio

#[tokio::main]
async fn main() {
    let result = fetch_data().await;
    println!("Result: {}", result);
}

async fn fetch_data() -> i32 {
    42
}

#[tokio::main] 属性可以直接在 async main 函数中使用 异步函数。使用起来也非常简单。tokio 特点:

模块/功能描述
tokio::spawn启动并发任务,返回 JoinHandle,用于等待任务完成。
tokio::net提供异步 TCP/UDP 网络支持,适用于创建高效的服务器和客户端。
tokio::fs异步文件操作,支持非阻塞的文件读写。
tokio::time提供异步定时器与时间相关功能,如 sleeptimeout
tokio::sync::mpsc提供异步通道,用于任务间通信。多生产者单消费者(mpsc)。
tokio::sync::oneshot一次性通道,用于发送单个信号或结果。
tokio::sync::Mutex异步互斥锁,用于保护共享数据,防止并发访问。
tokio::sync::RwLock异步读写锁,允许多个任务同时读取或一个任务写入。
tokio::sync::Semaphore信号量,用于控制并发任务的数量。
tokio::sync::Notify异步通知机制,类似条件变量,允许任务间通知。
tokio::time::timeout为异步操作设置超时时间,防止任务长时间未完成。
tokio_stream::Stream异步流,允许按需产生多个值,类似异步迭代器。
tokio_stream::StreamExtStream 扩展方法,如 for_eachcollect,用于处理异步流中的数据。
tokio::join!并行执行多个异步任务,等待它们都完成。
tokio::select!从多个异步任务中选择最先完成的一个,适合处理多个任务的竞争条件。

async_std 异步

async_std 是可移植 Rust 软件的基础,是一组最小且经过实战测试的共享抽象,适用于更广泛的 Rust 生态系统。它提供了 std 类型,例如Future和Stream 、对语言原语的库定义操作、标准宏、 I/O和多线程等等。

  • 使用 block_on
use async_std::task;

async fn learn_song() -> i32 {
    123
}

fn main() {
    println!("Hello, world!");

    task::block_on(async {
        let a = learn_song().await;
        println!("{}",a)
    })
}
  • 使用 async_std::main 属性
#[async_std::main]
async fn main() {
    let a = async { 1u8 };
    let b = async { 2u8 };
    assert_eq!(a.join(b).await, (1u8, 2u8))
}

PinUnpin

由于异步任务的生命周期不固定,Rust 使用 Pin 来确保异步任务在内存中的位置不会被改变。

  • Pin:表示一个固定在内存中的指针,确保异步任务在未完成时不会被移动,因为移动可能会导致不安全行为(比如悬垂指针)。
  • Unpin:标记某个类型可以安全地在内存中移动。大多数类型默认是 Unpin,但自引用类型(如生成器和 Future)需要 Pin 来保持其内存位置稳定。
async fn example() {
    let a = String::from("hello");
    let future = async {
        println!("{}", a);  // 引用了局部变量 `a`
    };
    future.await;
}

小结

Rust 的异步编程通过 Future trait 实现惰性执行,它使用 async/await 语法来简化异步代码的编写。异步任务由执行器(如 Tokioasync-std)调度并轮询,确保任务在非阻塞的环境下执行。Rust 的设计确保异步操作具有零成本抽象,同时通过 PinUnpin 机制,确保异步任务在未完成时不会被移动,避免内存安全问题。这种高效的并发模型,使 Rust 能够处理大量异步任务,同时保持性能和安全性。