异步 Rust:基本概念

2025-06-07

异步 Rust:基本概念

TL;DR:我将尝试对异步 Rust 周围的一些概念进行易于理解的说明:async、await、Future、Poll、Context、Waker、Executor 和 Reactor。

正如我在这里写的大多数内容一样,我们已经有一些与异步 Rust 相关的优秀内容。让我列举几个:

既然有这么多精彩的信息,为什么还要写呢?我的答案和 几乎我的 DEV 博客上的每篇文章:都是为了吸引那些对这些内容仍然有点难以理解的读者。

所以,如果你想学习中级水平的内容,可以直接阅读上面列出的内容。否则,我们继续吧 :)


异步/.await

异步 Rust(简称 async Rust)是通过async/.await语法来实现的。这意味着这两个关键字(async.await)是编写异步 Rust 的核心。那么,什么是异步 Rust?

异步书中指出异步是一种并发编程模型。并发意味着不同的任务将交替执行其活动;例如,任务 A 执行一些工作,将线程交给任务 B,任务 B 执行一些工作后再将其交还,等等。

不要将其与并行编程混淆,并行编程是指同时运行多个任务的编程。可以将并发和并行编程结合起来(例如,通过生成 Future),但我不会在这里讨论它,因为async/.await它用于实现并发编程,所以这里我主要关注并发编程。

简而言之,我们使用async关键字告诉 Rust 一个块或一个函数将是异步的。

// asynchronous block
async {
    // ...
}

// asynchronous function
async fn foo(){
    // ...
}
Enter fullscreen mode Exit fullscreen mode

但是, Rust 程序的异步性究竟意味着什么呢?它意味着它将返回该Future特征的一个实现。我将Future在下一节中详细介绍;目前,我们只需说 aFuture代表一个可能已准备好也可能尚未准备好的值即可。

我们使用关键字来处理Future异步块/函数返回的a .await。考虑下面这个有点傻的例子:

async fn foo() -> i32 {
    11
}

fn bar() {
    let x = foo();

    // it is possible to .await only inside async fn or block
    async {
        let y = foo().await;
    };
}
Enter fullscreen mode Exit fullscreen mode

在这种情况下,x不是i32,而是 trait 的实现Future(在本例中)。另一方面,impl Future<Output = i32>变量将是: 11yi32

另一种直观的理解方式是理解 Rust 会将脱糖

async fn foo() -> i32 {}
Enter fullscreen mode Exit fullscreen mode

变成这样

fn foo() -> impl Future<Output=i32>{}
Enter fullscreen mode Exit fullscreen mode

当然,这里没有任何异步操作。但如果foo()情况比较复杂,需要等待Mutex锁或监听网络连接,Rust 不会一直占用线程,而是会尽可能多地处理后续工作,foo()然后让线程去做其他事情,等到有更多工作可以做的时候再收回。

希望我们讲完FuturePoll和 等概念后,您能理解。现在,只要您对Wake的用法有个大概的了解就足够了asyncawait

请务必阅读async/.await Primer


期货

我认为说Futuretrait 是异步 Rust 的心脏并不为过。

AFuture是一种具有以下特征的特征:

  • 一种Output类型(i32在上面的例子中)。
  • 一个poll函数。

poll()是一个尽可能多地完成工作的函数,然后返回一个名为的枚举Poll

enum Poll<T> {
    Ready(T),
    Pending,
}
Enter fullscreen mode Exit fullscreen mode

如你所见,我对.await和 的描述poll()有点重叠。这是因为调用.await最终会调用poll()。稍后会详细介绍。

这个枚举是我之前所写内容的表示,Future 表示一个可能准备好也可能还没准备好的值。

这个函数背后的基本思想很简单:当有人调用poll()Future 时,如果它已经完全完成,它将返回,Ready(T)并且.await将返回T。否则,它将返回Pending

问题是,如果它再次出现Pending,我们该如何让它继续运转直至完成?答案很简单,就是反应堆。然而,在实现这一点之前,我们还有一些工作要做。


轮询、上下文、唤醒器、执行器和反应器

好多词啊!不过我真心觉得把所有东西都放在一起更容易理解,因为结合上下文更容易理解它们的作用。为了说明这一点,我提出了一个简化的假设场景。

假设我们有一个Future通过async关键字 created 的实例。让我们回顾一下 Future 是什么:

#[must_use = "futures do nothing unless you `.await` or poll them"]
pub trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
Enter fullscreen mode Exit fullscreen mode

我不会 Pin 在这里介绍,因为它有点复杂,而且没有必要理解这里发生的事情。

正如上面的代码所暗示的,Rust 中的未来是惰性的,这意味着仅仅声明它们不会使它们运行。

现在,假设我们使用 来运行 Future .await。这里的“运行”意味着将其传递给将来会调用的“执行器” poll()

但是执行器是什么呢?简单来说,它是一种调度算法,实际上会轮询 Future。所以,当你调用 时.await,执行器会负责执行工作。

好的,我们调用了.await,future 被轮询并返回了Ready<T>。发生了什么?.await将会返回T,执行器将清除该 Future,因此它不会再次被轮询。

或者,如果轮询的未来无法完成所有工作,它将返回Pending

收到 后Pending,执行器将不会再次轮询 Future,除非它被告知。那么谁来通知它呢?“反应堆”。它将调用作为函数参数传递的wake()上的函数。这使得执行器知道相关任务已准备好继续执行。Wakerpoll()

但反应堆是什么?它是执行者的兄弟。当执行者在奥林匹斯山上管理一切,聆听 祈祷.await反应堆位于冥府号上,与系统I/O协同工作,承担着繁重的工作。反应堆将知道poll再次到达那个未来的最佳时机,并且它会发出指令wake()

那么,刚开始阅读 Rust 异步内容的你是否应该担心 executor 和 reactor 在后台是如何工作的呢?其实不必。为什么?因为当我们谈论 executor 和 reactor 时,我们已经在谈论运行时了;而当我们谈论运行时时,我们通常指的是Tokio。事实上,用executorreactor这样的名字来调用它,就已经符合 Tokio 的命名法了。所以,最后,你所要做的就是将 Tokio 融入到你的项目中。通常的做法是在main函数前使用它的过程宏:

#[tokio::main]
async fn main(){
  // your async code
}
Enter fullscreen mode Exit fullscreen mode

还是关于反应堆,Jon 花了 45 分钟在黑板上画图来解释,我可不敢说我能做得更好。所以,如果你想深入了解这个细节,可以看看上面的链接。


总结

让我们回顾一下:

  • async用于创建异步块或函数,使其返回一个Future
  • .await将等待未来的完成并最终返回值(或错误,这就是为什么通常在中使用问号运算符的.await?原因)。
  • Future是异步计算的表示,是可能已准备好或尚未准备好的值,由Poll枚举的变体表示。
  • Poll是未来返回的枚举,其变体可以是Ready<T>Pending
  • poll()是执行 Future 并使其完成的函数。它接收一个Context参数,并由执行器调用。
  • Context是 的包装器Waker
  • Waker是一种包含将被反应器wake()调用的函数的类型,告诉执行器它可能会再次轮询未来。
  • Executor是一个通过重复调用来执行未来的调度程序poll()
  • Reactor有点像事件循环,负责唤醒待处理的未来。

好的,当然还有很多内容要谈,例如SendSync特征Pinning等等,但我认为,对于初学者的帖子来说,我们已经说得足够多了。

下次再见!

封面艺术由TK创作。


编辑 — 2021年9月1日:我做了一些修改,因为我意识到我为了简化某些内容而做出的努力,反而让它们听起来不对劲。这个问题可能还会在文中反复出现,所以如果你看到我为了简洁而牺牲了正确性,指出来。

文章来源:https://dev.to/rogertorres/asynchronous-rust-basic-concepts-44ed
PREV
如何构建基本的 RAG 应用程序
NEXT
🎥简单的下拉菜单栏 | HTML 和 CSS🙌