参考博客:https://hegdenu.net/posts/understanding-async-await-2/

在前一部分中,我们研究了最简单的异步函数。一个简单的 async 函数,它不做任何 async 的事情。然后我们写了一个定制的 future 来做同样的事情。这样,我们就明白了为什么我们最简单的 future 是异步的。为什么它在 await 之前不执行内容(因为异步函数返回的是一个 future,而 future 是要扔到异步运行时里去才能被执行的)。我们的 future 只会返回 Poll::Ready,让我们看看当返回 Poll::Pending 时会发生什么

how does a pending future get woken?

First,let’s recap what happens when a future gets polled,We can create an even simpler future than the Hello World one。This future will do nothing except return <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Ready</font>,We don’t even need any members for this
  1. use std::{future::Future, task::Poll};
  2. struct Ready;
  3. impl Future for Ready {
  4. type Output = ();
  5. fn poll(
  6. self: std::pin::Pin<&mut Self>,
  7. _cx: &mut std::task::Context<'_>,
  8. ) -> std::task::Poll<Self::Output> {
  9. println!("Ready: poll()");
  10. Poll::Ready(())
  11. }
  12. }
在第1部分中,我们可视化了我们编写的 future 状态机,尽管 Ready 的 future 更加简单,但让我们检查一下状态机

异步理解之路二 - 图1

现在让我们用一个函数来包装我们的 future
  1. fn ready() -> Ready {
  2. Ready {}
  3. }
  4. // 或者这样也行
  5. fn ready() -> impl Future<Output = ()> {
  6. Ready {}
  7. }
因为 Ready 实现了 Future trait,所以我们可以 await 这个函数
  1. use std::future::Future;
  2. use std::task::{Context, Poll};
  3. use std::pin::Pin;
  4. struct Ready;
  5. impl Future for Ready {
  6. type Output = ();
  7. fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
  8. println!("Ready: poll()");
  9. Poll::Ready(())
  10. }
  11. }
  12. fn ready() -> Ready {
  13. Ready {}
  14. }
  15. // 或者这样也行
  16. fn ready() -> impl Future<Output = ()> {
  17. Ready {}
  18. }
  19. #[tokio::main]
  20. async fn main() {
  21. println!("Before ready().await");
  22. ready().await;
  23. println!("After ready().await");
  24. }
  25. PS D:\Rust\Work_Place\hello_work> cargo run
  26. Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.09s
  27. Running `target\debug\hello_work.exe`
  28. Before ready().await
  29. Ready: poll()
  30. After ready().await
What happens behind the <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">.await</font> syntax is that the <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">poll</font> function gets called. As it returned <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Ready</font>, the result is passed straight back to the caller. 为了完整起见,下面是使用 Ready future 的程序的序列图

异步理解之路二 - 图2

Let’s try to create an equivalent of the ready future, but pending. The same as for <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Ready</font>, we’ll create a unit <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">struct</font>. This time called <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Pending</font>. Then we’ll implement the <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Future</font> trait for it
  1. use std::future::Future;
  2. use std::task::{Context, Poll};
  3. use std::pin::Pin;
  4. struct Pending;
  5. impl Future for Pending {
  6. type Output = ();
  7. fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
  8. println!("Pending: poll()");
  9. Poll::Pending
  10. }
  11. }
  12. fn pending() -> Pending {
  13. Pending {}
  14. }
  15. // 或者这样也行
  16. fn pending() -> impl Future<Output = ()> {
  17. Pending {}
  18. }
  19. #[tokio::main]
  20. async fn main() {
  21. println!("Before pending().await");
  22. pending().await;
  23. println!("After pending().await");
  24. }
您可能会问自己,为什么我们一直把 future 包装在函数中
  • look at a function that can be <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">await</font>ed like an <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">async</font> function can be
  • By constructing the future in our own function, we can hide the details from the user of our API
  • We can even go so far as to prevent the construction of our future outside of our own crate or module
  • This makes backwards compatibility easier
  1. PS D:\Rust\Work_Place\hello_work> cargo run
  2. Compiling hello_work v0.1.0 (D:\Rust\Work_Place\hello_work)
  3. Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.83s
  4. Running `target\debug\hello_work.exe`
  5. Before pending().await
  6. Pending: poll()
  7. // 卡在这里阻塞了

不要等到项目结束。这个项目不会结束。它将永远挂在那里。它不会占用太多的 CPU。它不会阻塞线程的执行。但它不会走得更远。同样清楚的是 poll() 只被调用一次!Our future is never polled again after returning <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Pending</font>,It’s true that this future seems broken in all sorts of ways。But it can be useful in certain scenarios, like tests。Back to why <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Pending</font> is hanging our program,Let’s check our state machine,Maybe the state machine can explain what’s happening

异步理解之路二 - 图3

我们用虚线来表示到 Final 的路径。这表示该对象可能永远不会被 drop。我们没有很好的方法在序列图上表示这个。Pending 的状态机与 Ready 的状态机非常相似

异步理解之路二 - 图4

从上面的序列图来看,并不完全清楚。我们看到,我们的 futurePoll::Pending 返回给 async main() 函数。但是我们没有看到 println! 下面的调用。这个执行流程其实是一个小谎言。我们需要更深入地了解正在发生的事情

unwrapping async main()

  • Part of that lie is how **<font style="color:#117CEE;background-color:rgb(238, 238, 238);">async main()</font>** works. Specifically what the **<font style="color:#117CEE;background-color:rgb(238, 238, 238);">#[tokio::main]</font>** macro does
  • The other part is what **<font style="color:#117CEE;background-color:rgb(238, 238, 238);">.await</font>** does underneath.
Let’s unwrap **<font style="color:#DF2A3F;background-color:rgb(238, 238, 238);">#[tokio::main]</font>** and have a look at what is inside
  1. use std::future::Future;
  2. use std::task::{Context, Poll};
  3. use std::pin::Pin;
  4. struct Pending;
  5. impl Future for Pending {
  6. type Output = ();
  7. fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
  8. println!("Pending: poll()");
  9. Poll::Pending
  10. }
  11. }
  12. fn pending() -> Pending {
  13. Pending {}
  14. }
  15. // // 或者这样也行
  16. // fn pending() -> impl Future<Output = ()> {
  17. // Pending {}
  18. // }
  19. fn main() {
  20. let body = async {
  21. println!("Before pending().await");
  22. pending().await;
  23. println!("After pending().await");
  24. };
  25. return tokio::runtime::Builder::new_multi_thread()
  26. .enable_all()
  27. .build()
  28. .expect("Failed building the Runtime")
  29. .block_on(body);
  30. }
This was done with Rust Analyzer’s <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Expand macro recursively</font> command. We can now see that the body of our <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">async main()</font> function is actually placed in an <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">async</font> block. Then a new runtime is created and given the <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">async</font> block to run. To clarify, an <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">async</font> block is also just a future ! We now have a better understanding of what our <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">async main()</font> function was actually doing

异步理解之路二 - 图5

We now see that it’s actually the async runtime that is calling <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">poll()</font> on the future which is driving the main task. There’s something important to note when a future awaits some sub-future which returns <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Pending</font>. Then the future also returns <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Pending</font> back to its caller. In this case that goes back to the async runtime. 当被轮询的任务返回 Poll::Pending 时,任务本身进入睡眠状态。The async runtime then picks another task to poll (it might poll the same task again if it can be polled) . In order for our task to be polled again, it needs to wake up. But maybe there are no tasks which are scheduled to be polled (scheduled to be polled means awake) . In that case, the async runtime parks the thread until a task gets woken. So, the big question is: when does a task wake up? Answer: when the waker wakes it

the waker:What is a waker? Where can I get one?

When we’re talking about a waker, we’re talking about std::task::Waker. It’s a struct in the standard library. What do the docs say?
  1. A Waker is a handle for waking up a task by notifying its executor that it is ready to be run
So now we know, we can use the waker to wake up our task. You call wake() or wake_by_ref() on the waker for a task. Then the task wakes up and polls the future again. But where do we get one of these from. More importantly, where do we get a waker for our task. 之前第一部分说过:We also don’t need the context for now, so we’ll skip that too. This was in reference to the second parameter to the <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">poll</font> function: **<font style="color:#DF2A3F;background-color:rgb(238, 238, 238);">cx: &mut Context<'_></font>** 上下文是将当前异步任务的信息传递给 future 任务的方式 https://doc.rust-lang.org/std/task/struct.Context.html > 目前,上下文仅用于提供对 &Waker 的访问,该 &Waker 可用于唤醒当前任务 > 实际上,Context 只有两个方法
  • 第一个是 from_waker,它从对 waker 的引用构造一个上下文
  • 第二个是 waker,它接受对上下文的引用并返回对该 waker 的引用
We want to write a future that returns <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Pending</font> but doesn’t hang forever. We need to make 2 changes to our <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Pending</font> future
  • Change 1 is to return <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Pending</font> only once. From the second call to <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">poll()</font>, we will instead return <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Ready</font>
  • As we’ve seen, <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">poll()</font> won’t get called again until the task gets woken. So change 2 is to wake our task. And we can do this before we return <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Pending</font>
We’re going to call this future <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">YieldNow</font>. Different to our <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Ready</font> and <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Pending</font> futures, we need some state
  1. use std::future::Future;
  2. use std::task::{Context, Poll};
  3. use std::pin::Pin;
  4. struct YieldNow {
  5. yielded: bool,
  6. }
  7. impl Future for YieldNow {
  8. type Output = ();
  9. fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
  10. println!("YieldNow: poll()");
  11. if self.yielded == true {
  12. return Poll::Ready(());
  13. }
  14. self.yielded = true;
  15. cx.waker().wake_by_ref();
  16. Poll::Pending
  17. }
  18. }
Our <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">YieldNow</font> struct has a single field. This determines whether we’ve “yielded” yet. Yielding in this context means returning control to the async runtime. So “yielding” is really just “returning <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Pending</font>“. If we’ve already yielded, we return <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Ready</font>. If we haven’t, we set <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">yielded</font> to <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">true</font>. Then we wake the waker ! And finally return <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Pending</font>. But because we’ve already woken our task, we’ve indicate that we’re ready to be polled again. So our task won’t hang ! As usual, let’s wrap our future in a function
  1. use std::future::Future;
  2. use std::task::{Context, Poll};
  3. use std::pin::Pin;
  4. struct YieldNow {
  5. yielded: bool,
  6. }
  7. impl Future for YieldNow {
  8. type Output = ();
  9. fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
  10. println!("YieldNow: poll()");
  11. if self.yielded == true {
  12. return Poll::Ready(());
  13. }
  14. self.yielded = true;
  15. cx.waker().wake_by_ref();
  16. Poll::Pending
  17. }
  18. }
  19. fn yield_now() -> YieldNow {
  20. YieldNow { yielded: false }
  21. }
  22. fn main() {
  23. let body = async {
  24. println!("Before yieldNow().await");
  25. yield_now().await;
  26. println!("After yieldNow().await");
  27. };
  28. return tokio::runtime::Builder::new_multi_thread()
  29. .enable_all()
  30. .build()
  31. .expect("Failed building the Runtime")
  32. .block_on(body);
  33. }
  34. // 输出
  35. Before yield_now().await
  36. YieldNow: poll()
  37. YieldNow: poll()
  38. After yield_now().await
正如上面提到的,我们把控制返回到运行时称为屈服(yield),This is what happens at every <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">await</font> point that returns pending (remember that when a future <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">await</font>s another future and receives <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Pending</font> it also returns <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Pending</font>) . Our <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">yield_now()</font> function is voluntarily yielding control to the runtime. It’s voluntarily because the task isn’t actually waiting for anything. There is a function to do this in Tokio: tokio::task::yield_now. Let’s have a look at the state machine for <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">YieldNow</font> 异步理解之路二 - 图6 1. The future starts with **<font style="color:#117CEE;background-color:rgb(238, 238, 238);">yielded = false</font>** 2. The first time it is polled, it returns **<font style="color:#117CEE;background-color:rgb(238, 238, 238);">Poll::Pending()</font>** and transitions to **<font style="color:#117CEE;background-color:rgb(238, 238, 238);">yielded = true</font>** 3. From there, the future will return **<font style="color:#117CEE;background-color:rgb(238, 238, 238);">Poll::Ready(())</font>** from any further calls to **<font style="color:#117CEE;background-color:rgb(238, 238, 238);">poll()</font>** 异步理解之路二 - 图7 The <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">YieldNow</font> future is very similar to the <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Pending</font> future. Until it calls <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">wake_by_ref()</font> on the waker. The waker then calls to the async runtime to <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">schedule()</font> the current task. Now the task is scheduled. And so we see a difference when the task returns <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Pending</font> back to the runtime. The runtime now does have a task ready to poll (scheduled). So it doesn’t park the thread. Instead it polls the task again straight away. This time, our <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">YieldNow</font> future returns <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">Poll::Ready</font>. Since the task that we called <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">block_on</font> with is finished, the runtime returns control to <font style="color:rgb(51, 51, 51);background-color:rgb(238, 238, 238);">main()</font>. And it returns the value from our future. In this case there is no value, so it returns the unit type. And now we understand how a pending future gets woken !