rust, future & generator

    技术2022-07-29  76

    rust, future & generator

    future traitfn() -> impl Futureasync fn() 以及闭包async {}await

    探讨future的不同写法。以及其中的差异。

    future trait

    自定义实现一个 trait 特征。需要实现 poll 方法。该方法返回一个枚举值: Poll<T>, Ready 代表完成包裹了真正的返回数据;Pending 意味着任务还未完成。

    pub trait Future { /// The type of value produced on completion. #[stable(feature = "futures_api", since = "1.36.0")] type Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>; } pub enum Poll<T> { /// Represents that a value is immediately ready. #[stable(feature = "futures_api", since = "1.36.0")] Ready(#[stable(feature = "futures_api", since = "1.36.0")] T), /// Represents that a value is not ready yet. /// /// When a function returns `Pending`, the function *must* also /// ensure that the current task is scheduled to be awoken when /// progress can be made. #[stable(feature = "futures_api", since = "1.36.0")] Pending, }

    Executor 会来调用 Future的 poll 方法根据返回值的不同,决定下一步的动作。

    fn() -> impl Future

    显式返回一个future trait。

    fn my_fut() -> impl Future<Output = ()> { future::ready(()) }

    async fn() 以及闭包

    代码示例如下:

    async fn ss() -> u32 { 0 }

    async 关键字,将函数的原型修改为返回一个future trait。然后将执行的结果包装在一个新的future中返回。大致相当于:

    fn ss() -> impl Future<Output = u32> { async { 0 } }

    从这里可以看到,async fn 永远是返回 Ready(T),而没有办法返回 Pending的。

    async {}

    async 代码块实现了一个匿名的 Future Trait,名为GenFuture,实现了 Future,并包裹一个 generator。如下所示:

    pub const fn from_generator<T>(gen: T) -> impl Future<Output = T::Return> where T: Generator<ResumeTy, Yield = ()>, { struct GenFuture<T: Generator<ResumeTy, Yield = ()>>(T); // We rely on the fact that async/await futures are immovable in order to create // self-referential borrows in the underlying generator. impl<T: Generator<ResumeTy, Yield = ()>> !Unpin for GenFuture<T> {} impl<T: Generator<ResumeTy, Yield = ()>> Future for GenFuture<T> { type Output = T::Return; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { // Safety: Safe because we're !Unpin + !Drop, and this is just a field projection. let gen = unsafe { Pin::map_unchecked_mut(self, |s| &mut s.0) }; // Resume the generator, turning the `&mut Context` into a `NonNull` raw pointer. The // `.await` lowering will safely cast that back to a `&mut Context`. match gen.resume(ResumeTy(NonNull::from(cx).cast::<Context<'static>>())) { GeneratorState::Yielded(()) => Poll::Pending, GeneratorState::Complete(x) => Poll::Ready(x), } } } GenFuture(gen) }

    GenFuture 是一个实现了 Future 的 generator。因此在 Executor 执行这样的 Future 会进入相应的 poll 方法,接下来 gen.resume 将会驱动 generator执行 async {} 的内容。如果 async {} 暂时无法完成,则返回 Yielded,进而 gen.resume 返回 Poll::Pending,暂停执行。一旦恢复执行,generator 返回 Complete,gen.resume 返回Poll::Ready(x),Future 任务完成。

    此外,如果 async {} 执行中嵌套进入了下一级的 async{},则递进执行下一级 generator。

    await

    每一个await本身就像一个小型的执行器,在循环中查询任务的状态。 await 执行 generator 本身 async {} 代码。大致的逻辑就是循环调用Future的 poll 方法。如果返回 Pending,则 yield,否则退出循环,结束 Future。

    示意代码如下:

    loop { match polling_future_block() { Pending => yield, Ready(x) => break } }
    Processed: 0.009, SQL: 9