#run-time #task #thread #async #context #spawn #spawner

spawns-compat

线程上下文任务创建器的异步运行时检测

4 个版本

0.2.1 2024年5月6日
0.2.0 2024年5月5日
0.1.1 2024年5月4日
0.1.0 2024年5月4日

#1417异步

Download history 186/week @ 2024-04-28 377/week @ 2024-05-05 3/week @ 2024-05-12 35/week @ 2024-05-19 10/week @ 2024-05-26 4/week @ 2024-06-02 6/week @ 2024-06-09 1/week @ 2024-06-16

每月175 次下载
2 个 crate 中使用 (通过 spawns)

Apache-2.0

27KB
455

Spawns

crates.io github-ci codecov docs.rs Apache-2.0

Rust 的线程上下文任务创建器,简化异步运行时无关的编码。

动机

目前,Rust 没有一个标准的异步运行时。这使我们面临选择一个的困境,并使得创建运行时无关的库变得非常困难。我们面临的最大挑战是如何创建任务?

spawns 为 Rust 的 std 和异步运行时提供了一个线程上下文任务创建器。一旦提供,我们就可以以运行时无关的方式创建任务。与其他运行时无关的 io、计时器、通道等 crate 一起,我们能够轻松编写运行时无关的代码。

异步运行时的 API

/// Thin wrapper around task to accommodate possible new members.
#[non_exhaustive]
pub struct Task {
    pub id: Id,
    pub name: Name,
    pub future: Box<dyn Future<Output = ()> + Send + 'static>,
}

/// Trait to spawn task.
pub trait Spawn {
    fn spawn(&self, task: Task);
}

/// Scope where tasks are [spawn]ed through given [Spawn].
pub struct SpawnScope<'a> {}

/// Enters a scope where new tasks will be [spawn]ed through given [Spawn].
pub fn enter(spawner: &dyn Spawn) -> SpawnScope<'_>;

异步运行时必须执行两项操作以适应其他运行时无关的 API。

  1. 实现 Spawn 以创建异步任务。
  2. 在所有执行器线程中调用 enter

客户端的 API

impl<T> JoinHandle<T> {
    /// Gets id of the associated task.
    pub fn id(&self) -> Id {}

    /// Cancels associated task with this handle.
    ///
    /// Cancellation is inherently concurrent with task execution. Currently, there is no guarantee
    /// about promptness, the task could even run to complete normally after cancellation.
    pub fn cancel(&self) { }

    /// Attaches to associated task to gain cancel on [Drop] permission.
    pub fn attach(self) -> TaskHandle<T> { }
}

impl<T> Future for JoinHandle<T> {
    type Output = Result<T, JoinError>;
}

/// Spawns a new task.
///
/// # Panics
/// 1. Panic if no spawner.
/// 2. Panic if [Spawn::spawn] panic.
pub fn spawn<T, F>(f: F) -> JoinHandle<T>
where
    F: Future<Output = T> + Send + 'static,
    T: Send + 'static;

该 API 可以创建、连接和取消任务,就像 tokiosmolasync-std 一样。

关注点

  1. 装箱?是的,它需要 GlobalAlloc
  2. 即使是入口 future 也要装箱吗?不,但是 try_id() 将返回 None。我想我们可以提供一些包装函数。
  3. no_std?不,它目前需要 thread_local!。一旦稳定,我们可以将其移动到 #[thread_local]
  4. spawn_local!Send future 有帮助吗?不,至少目前还没有。我只看到 async-global-executor 能够自由地使用 spawn_local。我认为这是 Rust 的责任,不要将拥有 !Send 的 future 当作 !Send。这样,我们创建 !Send future 的可能性就会很小。请参阅 Async Rust needs Await and 'thread for Send Future 了解我的想法。对于首先捕获 !Send 并存储线程局部 !Send 的 future,它们需要当前线程的 executor。

  1. spawns-core 为异步运行时提供 Spawnenter() 以设置线程上下文任务 spawner。
  2. spawns-compat 通过功能门提供对 tokiosmolasync-global-executor(由 async-std 使用) 的兼容性。
  3. spawns-executor 提供了具有当前线程 executor 和多线程 executor 的完整功能的 block_on
  4. spawns 导出所有上述包,包括功能门 tokiosmolasync-global-executor。此外,它还提供功能门 executor 以包含 spawns-executor

示例

请参阅 示例。这里列出了一个最小的无运行时依赖的 echo 服务器以供演示。

use async_net::*;
use futures_lite::io;

pub async fn echo_server(port: u16) {
    let listener = TcpListener::bind(("127.0.0.1", port)).await.unwrap();
    println!("Listen on port: {}", listener.local_addr().unwrap().port());
    let mut echos = vec![];
    let mut id_counter = 0;
    loop {
        let (stream, remote_addr) = listener.accept().await.unwrap();
        id_counter += 1;
        let id = id_counter;
        let handle = spawns::spawn(async move {
            eprintln!("{:010}[{}]: serving", id, remote_addr);
            let (reader, writer) = io::split(stream);
            match io::copy(reader, writer).await {
                Ok(_) => eprintln!("{:010}[{}]: closed", id, remote_addr),
                Err(err) => eprintln!("{:010}[{}]: {:?}", id, remote_addr, err),
            }
        })
        .attach();
        echos.push(handle);
    }
}

为了使其正常工作,您只需要设置线程上下文任务 spawner。

许可证

Apache-2.0

依赖项

~0–10MB
~99K SLoC