展开描述
异步绿色线程。
§什么是 Task?
任务(task)是一个轻量级、非阻塞的执行单元。任务与 OS 线程类似,但它们不是由 OS 调度器管理,而是由 Tokio runtime 管理。这种模式的另一个名称是绿色线程。如果你熟悉 Go 语言的 goroutine、Kotlin 的协程 或 Erlang 的进程,你可以将 Tokio 的任务视作与之类似的概念。
关于任务的关键要点包括:
-
任务是轻量级的。由于任务由 Tokio runtime(而非操作系统)进行调度,创建新任务或在任务之间切换不需要上下文切换,开销相当低。大量任务的创建、运行和销毁成本都相当低,尤其是与 OS 线程相比。
-
任务的调度是协作式的。大多数操作系统实现的是抢占式多任务。在这种调度技术中,操作系统允许每个线程运行一段时间,然后抢占它,临时暂停该线程并切换到另一个线程。而任务则实现协作式多任务。在协作式多任务中,任务被允许运行到它主动让出(yield)为止,这向 Tokio runtime 的调度器表明它当前无法继续执行。当任务让出时,Tokio runtime 会切换到执行下一个任务。
-
任务是非阻塞的。通常,当 OS 线程执行 I/O 或必须与另一个线程同步时,它会阻塞,让操作系统调度另一个线程。当任务无法继续执行时,它必须让出(yield),从而允许 Tokio runtime 调度另一个任务。任务通常不应执行可能阻塞线程的系统调用或其他操作,因为这也会阻止同一线程上的其他任务运行。本模块提供了在异步上下文中运行阻塞操作的 API。
§使用 Task
此模块提供以下用于处理任务的 API:
§Spawning
本模块中最重要的函数可能是 task::spawn。该函数可视为标准库 thread::spawn 的异步等价版本。它接受一个 async 块或其他 future,并创建一个新任务以并发地运行该工作:
use tokio::task;
task::spawn(async {
// perform some work here...
});与 std::thread::spawn 类似,task::spawn 返回一个 JoinHandle 结构体。JoinHandle 本身也是一个 future,可用于 await 已派生任务的输出。例如:
use tokio::task;
let join = task::spawn(async {
// ...
"hello world!"
});
// ...
// Await the result of the spawned task.
let result = join.await?;
assert_eq!(result, "hello world!");同样,与 std::thread 的 JoinHandle 类型类似,如果已派生任务 panic,则 await 其 JoinHandle 将返回一个 JoinError。例如:
use tokio::task;
let join = task::spawn(async {
panic!("something bad happened!")
});
// The returned result indicates that the task failed.
assert!(join.await.is_err());spawn、JoinHandle 和 JoinError 在启用 “rt” feature flag 时可用。
§Cancellation
已派生任务可以使用 JoinHandle::abort 或 AbortHandle::abort 方法取消。当调用这些方法之一时,任务会在下一次在 .await 点让出时被通知关闭。如果任务已经处于空闲状态,则会尽快关闭,且在关闭之前不再运行一次。此外,关闭 Tokio runtime(例如通过从 #[tokio::main] 返回)会立即取消该 runtime 上的所有任务。
当任务被关闭时,它将在当前让出的 .await 处停止运行。所有局部变量都会通过运行其析构函数被销毁。一旦关闭完成,await JoinHandle 将失败并返回 取消错误。
请注意,终止一个任务并不保证它会以取消错误失败,因为它可能先正常完成。例如,如果在调用 abort 与任务结束之间的任何时刻,该任务都没有让出 runtime,那么 JoinHandle 将改为报告任务已正常退出。
请注意,使用 spawn_blocking 派生的任务无法被终止,因为它们不是异步的。如果你对 spawn_blocking 任务调用 abort,则不会产生任何效果,任务将继续正常运行。例外情况是该任务尚未开始运行;此时调用 abort 可能会阻止该任务启动。
请注意,对 JoinHandle::abort 的调用只是将任务调度为待取消状态,并在取消完成之前就会返回。要等待取消完成,可通过 await JoinHandle 来等待任务结束。类似地,JoinHandle::is_finished 方法在取消完成之前不会返回 true。
多次调用 JoinHandle::abort 与仅调用一次效果相同。
Tokio 还提供了 AbortHandle,它与 JoinHandle 类似,但它不提供等待任务完成的机制。每个任务只能拥有一个 JoinHandle,但可以拥有多个 AbortHandle。
§Blocking and Yielding
如上所述,在异步任务中运行的代码不应执行可能阻塞的操作。在运行其他任务的线程上,某个任务执行的阻塞操作将阻塞整个线程,从而阻止其他任务的运行。
为此,Tokio 提供了两个用于在异步上下文中运行阻塞操作的 API:task::spawn_blocking 和 task::block_in_place。
请注意,如果你在异步代码中调用非 async 方法,该非 async 方法仍处于异步上下文之中,因此你也应避免在那里执行阻塞操作。这包括在异步代码中被销毁的对象的析构函数。
§spawn_blocking
task::spawn_blocking 函数与上一节讨论的 task::spawn 函数类似,但后者是在 Tokio runtime 上派生非阻塞的 future,而前者则是在专门用于阻塞任务的线程池上派生阻塞函数。例如:
use tokio::task;
task::spawn_blocking(|| {
// do some compute-heavy work or call synchronous code
});与 task::spawn 一样,task::spawn_blocking 也会返回一个 JoinHandle,可用于 await 阻塞操作的结果:
let join = task::spawn_blocking(|| {
// do some compute-heavy work or call synchronous code
"blocking completed"
});
let result = join.await?;
assert_eq!(result, "blocking completed");§block_in_place
在使用多线程 runtime时,还可以使用 task::block_in_place 函数。与 task::spawn_blocking 类似,该函数允许在异步上下文中运行阻塞操作。但与 spawn_blocking 不同,block_in_place 通过将当前 worker 线程转变为阻塞线程,并把该线程上运行的其他任务转移到另一个 worker 线程上来工作。这可以通过避免上下文切换来提升性能。
例如:
use tokio::task;
let result = task::block_in_place(|| {
// do some compute-heavy work or call synchronous code
"blocking completed"
});
assert_eq!(result, "blocking completed");§yield_now
此外,本模块还提供了一个 task::yield_now 异步函数,它类似于标准库的 thread::yield_now。调用并 await 此函数将使当前任务让出给 Tokio runtime 的调度器,从而允许调度其他任务。最终,让出的任务将再次被 poll 以允许其执行。例如:
use tokio::task;
async {
task::spawn(async {
// ...
println!("spawned task done!")
});
// Yield, allowing the newly-spawned task to execute first.
task::yield_now().await;
println!("main task done!");
}模块§
结构体§
- Abort
Handle - 中止已生成 task 的 owned 权限,无须等待其完成。
- Id
- 一个不透明的 ID,用于在所有当前运行的 task 中唯一标识某个 task。
- Join
Error - Task 未执行完毕即失败。
- Join
Handle - 用于 join 一个 task(等待其终止)的 owned 权限。
- JoinSet
- A collection of tasks spawned on a Tokio 运行时。
- Local
Enter Guard LocalSet的上下文守护- Local
Key - task 局部数据的键。
- Local
Set - 在同一线程上执行的一组 task。
函数§
- block_
in_ place - 在当前线程上运行提供的阻塞函数,但不会阻塞 executor。
- id
- 返回当前正在运行的 task 的
Id。 - spawn
- 生成一个新的异步 task,返回一个
JoinHandle。 - spawn_
blocking - 在允许阻塞的线程上运行所提供的闭包。
- spawn_
local - 在当前
LocalSet或LocalRuntime上生成一个!Sendfuture。 - try_id
- 返回当前正在运行的 task 的
Id;若在 task 之外调用则返回None。 - yield_
now - Yields execution back to the Tokio 运行时。