跳到主要内容

spawn_blocking

搜索

函数 spawn_blocking 

Source
pub fn spawn_blocking<F, R>(f: F) -> JoinHandle<R> 
where F: FnOnce() -> R + Send + 'static, R: Send + 'static,
展开描述

在允许阻塞的线程上运行所提供的闭包。

通常, 在 future 中发出阻塞调用 或执行大量计算 而不让出是有问题的, 因为它可能会阻止 executor 继续驱动其他 future。 此函数在专用于阻塞操作的线程上 运行所提供的闭包。 请参阅 CPU-bound tasks and blocking code 部分了解更多。

通过此函数请求阻塞线程时, Tokio 会派生更多阻塞线程, 直到达到在 Builder 上配置的上限。 达到上限后, 任务将被放入队列中。 默认情况下, 线程上限非常大, 因为 spawn_blocking 经常用于 无法异步执行的 各种 IO 操作。 当你使用 spawn_blocking 运行 CPU-bound 代码时, 应牢记此较大的上限。 运行大量 CPU-bound 计算时, 应使用 semaphore 或其他同步原语 来限制并行执行的计算数量。 专门的 CPU-bound executor(如 rayon) 也可能是一个不错的选择。

此函数适用于 最终会自行完成的 非异步操作。 如果你想派生一个普通线程, 应改用 thread::spawn

请注意, 使用 spawn_blocking 派生的任务无法被中止, 因为它们不是异步的。 如果你对 spawn_blocking 任务调用 abort, 那么这将没有任何效果, 该任务将继续正常运行。 例外情况是该任务 尚未开始运行; 在这种情况下, 调用 abort 可能会阻止 该任务启动。

当你关闭 executor 时, 它将尝试 abort 所有任务, 包括 spawn_blocking 任务。 但是, spawn_blocking 任务 一旦开始运行 就无法被中止, 这意味着运行时关闭 将无限期地等待 所有已启动的 spawn_blocking 运行完毕。 你可以使用 shutdown_timeout 在某个超时后停止等待它们。 请注意, 这仍然不会取消任务—— 它们只是被允许 在该方法返回后 继续运行。 如果阻塞任务尚未开始运行, 则它可能被取消, 但这无法保证。

§When to use spawn_blocking vs dedicated threads

spawn_blocking 适用于有限的阻塞工作, 该工作最终会完成。 每次调用都会在任务持续期间 占用运行时阻塞线程池中的一个线程。 因此, 长时间运行的任务 会降低线程池的有效容量, 一旦线程池饱和 且工作被排队, 这可能会延迟 其他阻塞操作。

对于无限期运行 或长时间运行的工作负载 (例如, 后台 worker 或持久处理循环), 首选使用 thread::spawn 创建的专用线程。

经验法则:

  • Use spawn_blocking for short-lived blocking operations
  • Use dedicated threads for long-lived or persistent blocking workloads

请注意,如果你使用的是单线程运行时, 此函数仍会为阻塞操作派生其他线程。 current-thread 调度器的单个线程仅用于异步代码。

在简单的情况下,让闭包在创建时接受输入参数 并返回单个值(或结构体/元组等)就足够了。

对于更复杂的情况—— 需要在同步上下文中 来回流式传输数据—— mpsc 通道 提供 blocking_sendblocking_recv 方法, 可在非异步代码 (如由 spawn_blocking 创建的线程)中使用。

另一个选项是 SyncIoBridge, 适用于 同步上下文操作字节流的场景。 例如, 你可能使用 hyper 这样的异步 HTTP 客户端 来获取数据, 但使用为同步 I/O 编写的库 对响应体 进行复杂解析。

Finally, see also 与同步代码桥接 for discussions around the opposite case of using Tokio as part of a larger synchronous codebase.

§示例

传递一个输入值并接收计算结果:

use tokio::task;

// Initial input
let mut v = "Hello, ".to_string();
let res = task::spawn_blocking(move || {
    // Stand-in for compute-heavy work or using synchronous APIs
    v.push_str("world");
    // Pass ownership of the value back to the asynchronous context
    v
}).await?;

// `res` is the value returned from the thread
assert_eq!(res.as_str(), "Hello, world");

使用通道:

use tokio::task;
use tokio::sync::mpsc;

let (tx, mut rx) = mpsc::channel(2);
let start = 5;
let worker = task::spawn_blocking(move || {
    for x in 0..10 {
        // Stand in for complex computation
        tx.blocking_send(start + x).unwrap();
    }
});

let mut acc = 0;
while let Some(v) = rx.recv().await {
    acc += v;
}
assert_eq!(acc, 95);
worker.await.unwrap();