Tokio

基础概念

1. 同步 (Synchronous)

任务执行必须按顺序来,一个任务完成后,下一个任务才能开始。就像你去餐厅点餐,必须等上一个菜吃完 / 上完,才能点下一个。

2. 异步 (Asynchronous)

任务执行不需要等待上一个任务完成,发起任务后可以去做其他事,等任务完成后会收到 “通知”(回调 / 事件)。就像点餐时先点完所有菜,不用等上一个菜上,后厨同时准备,菜做好了服务员会喊你。

3. 并发 (Concurrency)

多个任务在同一时间段内交替执行(看似同时进行),但同一时刻只有一个任务在执行。就像你一边写代码,一边回微信,一边喝水 —— 其实是大脑快速切换,同一时刻只做一件事。

核心:任务切换(上下文切换),利用等待时间(比如 IO 等待)处理其他任务。

  • 异步编程是实现并发的常见方式;
  • 单核 CPU 只能实现并发,无法实现并行。

4. 并行 (Parallelism)

多个任务在同一时刻真正同时执行。就像你和同事一起写代码,你们俩同一时刻都在敲键盘 —— 需要多核 CPU / 多个进程支持。

  • 概念划分
    • 并发、并行,是逻辑结构的设计模式。
    • 同步、异步,是逻辑调用方式。
    • 并发、并行是异步的 2 种实现方式。

单线程异步

1
2
#添加完整功能的tokio到项目中
cargo add tokio -F full
1
2
3
4
5
6
7
8
9
10
11
12
13
use tokio::runtime;

async fn hi() {
println!("Hello!");
}

fn main() {
let rt = runtime::Builder::new_current_thread()
.enable_all()
.build()
.unwrap();
rt.block_on(hi());
}

等效于

1
2
3
4
5
6
7
8
async fn hi() {
println!("Hello!");
}

#[tokio::main(flavor = "current_thread")]
async fn main() {
hi().await;
}

注意

  1. 去除flavor = "current_thread"即可变为多线程异步
  2. async fn main() {}并非异步函数,通过安装cargo-expand可以了解到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#![feature(prelude_import)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;
async fn hi() {
{
::std::io::_print(format_args!("Hello!\n"));
};
}
fn main() {
let body = async {
hi().await;
};
#[allow(
clippy::expect_used,
clippy::diverging_sub_expression,
clippy::needless_return,
clippy::unwrap_in_result
)]
{
return tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("Failed building the Runtime")
.block_on(body);
}
}

多线程异步

函数解析

tokio::spawn()

示例代码

1
2
3
let task = tokio::spawn(async move {
watch_valorant(on_start, on_stop).await;
});

tokio::spawn 接受一个Future表达式,会立即返回一个 JoinHandle<T>,其中 T 是异步任务(async move { ... } 块)的返回值类型。

  • JoinHandle 是一个句柄,你可以用它来:
    • 等待任务完成: 使用 task.await。这将返回异步任务的返回值。
    • 取消任务: 使用 task.abort()

async move { ... }:
- 这是一个异步闭包(async closure)
- async: 关键字表明这是一个异步函数或闭包,它可以在等待 I/O 或其他异步操作时暂停执行,而不会阻塞整个线程。
- move: 关键字表示这个闭包会获取它所捕获的所有变量的所有权。这意味着闭包内部的代码将拥有这些变量,而不是仅仅借用它们。这对于在 tokio::spawn 中启动的任务非常重要,因为任务可能会在函数作用域之外执行,所以它需要拥有它自己的数据副本或所有权。

Mutex

use tokio::sync::Mutex;:
- tokio::sync::Mutex 是 Tokio 运行时提供的异步互斥锁
- 与标准的 std::sync::Mutex 不同,tokio::sync::Mutex 是异步的。这意味着当一个任务尝试获取锁但锁已经被另一个任务持有(即发生阻塞)时,它不会阻塞整个线程,而是会让出 CPU 时间,允许其他任务运行,直到锁被释放。这对于在异步环境中(如 Tauri 应用中)管理共享资源非常重要。
- 这里的 Mutex 用来确保在任何时候,只有一个任务可以访问和修改 Option<JoinHandle<()>>。这可以防止在启动新任务和取消旧任务时发生数据不一致的问题。