pub fn spawn_blocking<F, R>(f: F) -> JoinHandle<R> ⓘ展开描述
在允许阻塞的线程上运行所提供的闭包。
通常, 在 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_blockingfor short-lived blocking operations - Use dedicated threads for long-lived or persistent blocking workloads
请注意,如果你使用的是单线程运行时, 此函数仍会为阻塞操作派生其他线程。 current-thread 调度器的单个线程仅用于异步代码。
§Related APIs and patterns for bridging asynchronous and blocking code
在简单的情况下,让闭包在创建时接受输入参数 并返回单个值(或结构体/元组等)就足够了。
对于更复杂的情况——
需要在同步上下文中
来回流式传输数据——
mpsc 通道
提供 blocking_send 和
blocking_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();