Pinning

要轮询 Future ,必须使用一种称为Pin<T>的特殊类型,来固定 Future。如果您阅读了在上一节“Executing Futures and Tasks”中,the Future trait的解释,您会发现Pin来自self: Pin<&mut Self>,它是Future:poll方法的定义。但这究竟是什么意思,为什么我们需要它?

Why Pinning

固定(Pinning)能得到一个保证,就是确保永远不会移动对象。要了解这样是必要的,我们需要记起async/.await的工作原理。考虑以下代码:

  1. let fut_one = ...;
  2. let fut_two = ...;
  3. async move {
  4. fut_one.await;
  5. fut_two.await;
  6. }

在幕后,新建一个实现Future的匿名类型,它提供一个poll(轮询)方法,看起来像这样的:

  1. // 这个 `Future` 类型,由我们的 `async { ... }` 代码块生成而来
  2. struct AsyncFuture {
  3. fut_one: FutOne,
  4. fut_two: FutTwo,
  5. state: State,
  6. }
  7. // 是我们 `async` 代码块可处于的,状态列表
  8. enum State {
  9. AwaitingFutOne,
  10. AwaitingFutTwo,
  11. Done,
  12. }
  13. impl Future for AsyncFuture {
  14. type Output = ();
  15. fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
  16. loop {
  17. match self.state {
  18. State::AwaitingFutOne => match self.fut_one.poll(..) {
  19. Poll::Ready(()) => self.state = State::AwaitingFutTwo,
  20. Poll::Pending => return Poll::Pending,
  21. }
  22. State::AwaitingFutTwo => match self.fut_two.poll(..) {
  23. Poll::Ready(()) => self.state = State::Done,
  24. Poll::Pending => return Poll::Pending,
  25. }
  26. State::Done => return Poll::Ready(()),
  27. }
  28. }
  29. }
  30. }

poll先被调用,它将轮询fut_one。如果fut_one还未完成,AsyncFuture::poll将返回。 Future 对poll进行调用,将在上一个停止的地方继续。这个过程一直持续到 Future 能成功完成。

但是,如果我们有一个,使用引用的async代码块?例如:

  1. async {
  2. let mut x = [0; 128];
  3. let read_into_buf_fut = read_into_buf(&mut x); // &mut x
  4. read_into_buf_fut.await;
  5. println!("{:?}", x);
  6. }

这会编译成什么结构?

  1. struct ReadIntoBuf<'a> {
  2. buf: &'a mut [u8], // 指向下面的 `x`
  3. }
  4. struct AsyncFuture {
  5. x: [u8; 128],
  6. read_into_buf_fut: ReadIntoBuf<'what_lifetime?>,
  7. }

在这里,ReadIntoBuf Future 拿着一个引用,指向我们结构的其他字段,即x。但是,如果AsyncFuture被移动(move),x的位置也会移动,使存储在read_into_buf_fut.buf中的指针无效。

将 Future 固定到内存中的特定位置,可以避免此问题,从而可以安全地创建,对async代码块内部值的引用。

How to Use Pinning

Pin类型会包裹着指针类型,保证指针后面的值不会移动。例如,Pin<&mut T>Pin<&T>Pin<Box<T>>,所有的这些,都保证T不会移动。

大多数类型在移动时,都没有问题。这些类型实现了一种称为Unpin的 trait。Unpin类型指针可以与Pin自由放入或取出。例如,u8Unpin,所以Pin<&mut u8>表现就像正常&mut u8

某些函数需要,要求与之配合使用的 Future 是Unpin。要使用不是UnpinFuture要么Stream,配合那些那需要Unpin类型的函数,那您首先必须 pin the value,方法有两种:Box::pin(创建一个Pin<Box<T>>) 或者 pin_utils::pin_mut!宏(创建一个Pin<&mut T>)。Pin<Box<Fut>>Pin<&mut Fut>既可以用作 Future ,也实现了Unpin

例如:

  1. use pin_utils::pin_mut; // `pin_utils` is a handy crate available on crates.io
  2. // A function which takes a `Future` that implements `Unpin`.
  3. fn execute_unpin_future(x: impl Future<Output = ()> + Unpin) { ... }
  4. let fut = async { ... };
  5. execute_unpin_future(fut); // Error: `fut` does not implement `Unpin` trait
  6. // Pinning with `Box`:
  7. let fut = async { ... };
  8. let fut = Box::pin(fut);
  9. execute_unpin_future(fut); // OK
  10. // Pinning with `pin_mut!`:
  11. let fut = async { ... };
  12. pin_mut!(fut);
  13. execute_unpin_future(fut); // OK