pub fn spawn_blocking<F, R>(f: F) -> JoinHandle<R> ⓘ展开描述
在可以接受阻塞的线程上运行所提供的闭包。
一般来说,在不让步的 future 中发出阻塞调用或执行大量计算是有问题的,因为它可能会阻止执行器驱动其他 future 向前。此函数在专用于阻塞操作的线程上运行所提供的闭包。有关更多信息,请参阅CPU-bound 任务和阻塞代码部分。
当通过此函数请求阻塞线程时,Tokio 将生成更多的阻塞线程,直到达到 Builder 上配置的上限为止。达到上限后,任务将放入队列中。默认情况下,线程上限非常大,因为 spawn_blocking 通常用于无法异步执行的各种 IO 操作。当你使用 spawn_blocking 运行 CPU-bound 代码时,应牢记这个大上限。当运行许多 CPU-bound 计算时,应使用信号量或其他同步原语来限制并行执行的计算数量。专门的 CPU-bound 执行器(例如 rayon)也可能是合适的选择。
此函数用于最终自行完成的非异步操作。如果要生成普通线程,应改用 thread::spawn。
请注意,使用 spawn_blocking 生成的任务无法中止,因为它们不是异步的。如果对 spawn_blocking 任务调用 abort,那么这将没有任何效果,任务将继续正常运行。例外情况是如果任务尚未开始运行;在这种情况下,调用 abort 可能会阻止任务启动。
关闭执行器时,它将尝试 abort 所有任务,包括 spawn_blocking 任务。但是,spawn_blocking 任务一旦开始运行就无法中止,这意味着运行时关闭将无限期地等待所有已开始的 spawn_blocking 完成运行。你可以使用 shutdown_timeout 在某个超时后停止等待它们。请注意,这仍然不会取消任务——只是允许它们在该方法返回后继续运行。如果阻塞任务尚未开始运行,则可能会被取消,但这并不能保证。
§When to use spawn_blocking vs dedicated threads
spawn_blocking 用于最终完成的有界阻塞工作。每次调用都会在任务的整个持续时间内占用运行时阻塞线程池中的一个线程。因此,长期运行的任务会降低线程池的有效容量,一旦线程池饱和并且工作被排队,这可能会延迟其他阻塞操作。
对于无限运行或长时间运行的负载(例如后台工作进程或持久处理循环),首选使用 thread::spawn 创建的专用线程。
经验法则是:
- Use
spawn_blockingfor short-lived blocking operations - Use dedicated threads for long-lived or persistent blocking workloads
请注意,如果你使用的是单线程运行时,此函数仍会为阻塞操作生成其他线程。当前线程调度器的单个线程仅用于异步代码。
§Related APIs and patterns for bridging asynchronous and blocking code
在简单情况下,让闭包在创建时接受输入参数并返回单个值(或结构体/元组等)就足够了。
对于希望将数据流入或流出同步上下文的更复杂情况,mpsc channel 具有 blocking_send 和 blocking_recv 方法,可用于由 spawn_blocking 创建的线程等非异步代码。
另一种选择是 SyncIoBridge,用于同步上下文在字节流上操作的情况。例如,你可能使用 hyper 等异步 HTTP 客户端获取数据,但使用为同步 I/O 编写的库对有效负载正文执行复杂的解析。
最后,另请参阅与同步代码桥接,其中讨论了将 Tokio 作为更大同步代码库一部分使用的相反情况。
§示例
传递输入值并接收计算结果:
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");使用 channel:
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();