Pinning

优质
小牛编辑
127浏览
2023-12-01

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

Why Pinning

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

let fut_one = ...;
let fut_two = ...;
async move {
    fut_one.await;
    fut_two.await;
}

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

// 这个 `Future` 类型,由我们的 `async { ... }` 代码块生成而来
struct AsyncFuture {
    fut_one: FutOne,
    fut_two: FutTwo,
    state: State,
}

// 是我们 `async` 代码块可处于的,状态列表
enum State {
    AwaitingFutOne,
    AwaitingFutTwo,
    Done,
}

impl Future for AsyncFuture {
    type Output = ();

    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
        loop {
            match self.state {
                State::AwaitingFutOne => match self.fut_one.poll(..) {
                    Poll::Ready(()) => self.state = State::AwaitingFutTwo,
                    Poll::Pending => return Poll::Pending,
                }
                State::AwaitingFutTwo => match self.fut_two.poll(..) {
                    Poll::Ready(()) => self.state = State::Done,
                    Poll::Pending => return Poll::Pending,
                }
                State::Done => return Poll::Ready(()),
            }
        }
    }
}

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

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

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

这会编译成什么结构?

struct ReadIntoBuf<'a> {
    buf: &'a mut [u8], // 指向下面的 `x`
}

struct AsyncFuture {
    x: [u8; 128],
    read_into_buf_fut: ReadIntoBuf<'what_lifetime?>,
}

在这里,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

例如:

use pin_utils::pin_mut; // `pin_utils` is a handy crate available on crates.io

// A function which takes a `Future` that implements `Unpin`.
fn execute_unpin_future(x: impl Future<Output = ()> + Unpin) { ... }

let fut = async { ... };
execute_unpin_future(fut); // Error: `fut` does not implement `Unpin` trait

// Pinning with `Box`:
let fut = async { ... };
let fut = Box::pin(fut);
execute_unpin_future(fut); // OK

// Pinning with `pin_mut!`:
let fut = async { ... };
pin_mut!(fut);
execute_unpin_future(fut); // OK