如何在不使用任何外部依赖项的情况下执行异步/等待功能?

时间:2019-05-22 08:48:36

标签: asynchronous rust future

我正在尝试创建最简单的示例,使async fn hello()最终打印出Hello World!。这应该在没有任何外部依赖项(例如tokio,仅是普通Rust和std)的情况下发生。如果我们无需使用unsafe就可以完成任务,则可加分。

#![feature(async_await)]

async fn hello() {
    println!("Hello, World!");
}

fn main() {
    let task = hello();

    // Something beautiful happens here, and `Hello, World!` is printed on screen.
}
  • 我知道async/await仍然是夜间活动,并且在可预见的将来可能会发生变化。
  • 我知道有很多Future实现,我知道tokio的存在。
  • 我只是想教育自己了解标准图书馆期货的内部运作方式。

我无助,笨拙的努力

我模糊的理解是,首先,我需要Pin完成任务。所以我继续

let pinned_task = Pin::new(&mut task);

但是

the trait `std::marker::Unpin` is not implemented for `std::future::GenFuture<[static generator@src/main.rs:7:18: 9:2 {}]>`

因此,我想当然会Box,因此,我确定它不会在内存中移动。出乎意料的是,我遇到了同样的错误。

到目前为止我能得到的是

let pinned_task = unsafe {
    Pin::new_unchecked(&mut task)
};

这显然不是我应该做的。即使这样,也可以说我已经获得Pin的{​​{1}}的支持。现在,我需要以某种方式Future。为此,我需要一个poll()

因此,我尝试四处寻找如何使用Waker的方法。在doc上,获得Waker的唯一方法似乎是与另一个接受Waker的{​​{1}}一起。从那里我到达here,从那里here,我I缩在地板上开始哭泣。

1 个答案:

答案 0 :(得分:2)

期货堆栈的这一部分并不是很多人想要实现的。我看到的粗略估计可能会有10个左右的实际实现。

也就是说,您可以通过遵循所需的功能签名来填写执行程序的基本方面,而这些方面非常受限制:

// Using Nightly from 2019-05-21
#![feature(async_await)]

async fn hello() {
    println!("Hello, World!");
}

fn main() {
    drive_to_completion(hello());
}

use std::{
    future::Future,
    ptr,
    task::{Context, Poll, RawWaker, RawWakerVTable, Waker},
};

fn drive_to_completion<F>(f: F) -> F::Output
where
    F: Future,
{
    let waker = my_waker();
    let mut context = Context::from_waker(&waker);

    let mut t = Box::pin(f);
    let t = t.as_mut();

    loop {
        match t.poll(&mut context) {
            Poll::Ready(v) => return v,
            Poll::Pending => panic!("This executor does not support futures that are not ready"),
        }
    }
}

type WakerData = *const ();

unsafe fn clone(_: WakerData) -> RawWaker {
    my_raw_waker()
}
unsafe fn wake(_: WakerData) {}
unsafe fn wake_by_ref(_: WakerData) {}
unsafe fn drop(_: WakerData) {}

static MY_VTABLE: RawWakerVTable = RawWakerVTable::new(clone, wake, wake_by_ref, drop);

fn my_raw_waker() -> RawWaker {
    RawWaker::new(ptr::null(), &MY_VTABLE)
}

fn my_waker() -> Waker {
    unsafe { Waker::from_raw(my_raw_waker()) }
}

Future::poll开始,我们看到我们需要Pin确定的未来和ContextContext是由需要WakerRawWaker创建的。 RawWaker需要一个RawWakerVTable。我们以最简单的方式创建所有这些片段:

  • 由于我们不打算支持NotReady案例,因此我们实际上不需要为该案例做任何事情,而可以惊慌失措。这也意味着wake的实现可以是无操作的。

  • 由于我们没有努力提高效率,所以我们不需要为唤醒者存储任何数据,因此clonedrop基本上也可以是无操作的。

  • 确定未来的最简单方法是Box,但这并不是最有效的方法。


如果您想支持NotReady,最简单的扩展就是忙循环,永远轮询。一种更有效的解决方案是使用一个全局变量,该变量指示有人呼叫了wake并阻止了该情况的发生。