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

8
我正在尝试创建最简单的示例,以便能够使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的存在。
  • 我只是尝试了解标准库futures的内部工作原理。

我的无助、笨拙的努力

我模糊地理解,首先需要钉住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”和“Future”,现在我需要以某种方式进行“poll()”。为此,我需要一个“Waker”。
因此,我试图找到如何获取“Waker”的方法。在doc上看起来似乎唯一的方法是使用另一个接受“RawWaker”的“new_unchecked”。从那里我到达了here,然后到达了here,在那里我只能蜷缩在地板上哭泣。
1个回答

10

这部分期货技术并不打算由很多人来实现。据我所见,粗略估计可能只会有10个左右的实际实现。

话虽如此,你可以通过遵循所需的函数签名填写极其有限的执行器的基本方面:

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是从Waker创建的,它需要一个RawWakerRawWaker需要一个RawWakerVTable。我们以最简单的方式创建所有这些部分:
  • 由于我们不尝试支持NotReady情况,因此我们永远不需要为该情况做任何实际操作,而可以选择恐慌。这也意味着wake的实现可以是无操作的。

  • 由于我们不尝试效率,因此我们不需要为我们的唤醒器存储任何数据,因此clonedrop也可以基本上是无操作的。

  • 固定未来的最简单方法是将其Box,但这不是最有效的可能性。


如果要支持NotReady,最简单的扩展是具有忙循环,永远轮询。稍微更有效的解决方案是具有指示某人已调用wake并阻止其变为真的全局变量。


真棒!小问题:async/await 特性仍然在夜间版中吗(因此会出现 #![feature(async_await)] ),但 std::Future 接口现在稳定了,对吗? - Matteo Monti
@MatteoMonti 有点吗?Future已在夜间编译器中稳定下来,但该稳定尚未传播到beta通道。它肯定还没有传播到任何发布的Rust版本。 - Shepmaster
@MatteoMonti Future 将在 1.36.0 版本中稳定,但不会在当前稳定版本(1.34.0)中出现。您需要使用 async fn hellow 功能。 - hellow
1
这正是我们需要的,在同步 FFI 中使用异步函数。谢谢! - Tom Pridham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接