从浅到深-Tokio强大的Rust异步框架,常用的Api与实战案例
大家好,我是梦兽编程。欢迎回来与梦兽编程一起刷Rust的系列。微信公众号【梦兽编程】即可加入梦兽编程微信交流群与我交流。
语言只是我们程序员实现途径中的工具,语言的生态丰富会让我们做起事来事半功倍。从今天开始我们开始进入Rust的生态学习。
在Rust中同样支持异步编程,只不过一般我们不会使用Rust官方,而是使用第三方依赖Tokio。今天就我们进入新的篇章《Tokio从浅到深之旅》。
常用Api
使用一个开源软件前,我们最好过一遍它的Api这样可以方便你后面遇到业务场景做出对的选择。
1. tokio::task::spawn 与 tokio::task::JoinHandle
这是一个函数,用于将一个异步任务放入Tokio的任务调度器中进行执行。它接受一个返回Future
的异步函数作为参数,并返回一个JoinHandle
,您可以使用它来等待任务的完成或取消任务。
use tokio::task;
async fn async_task() {
// 异步任务的逻辑
println!("Running async task");
}
#[tokio::main]
async fn main() {
let handle = tokio::task::spawn(async_task());
// 等待异步任务完成
// JoinHandle
handle.await.unwrap();
}
2. tokio::task::spawn_blocking
这是一个函数,用于将一个阻塞的操作转换为异步任务,并在Tokio的线程池中执行。这对于需要执行阻塞操作(如CPU密集型计算)的场景非常有用,以避免阻塞整个Tokio运行时。
就是说如果现在有一个不是async的
Future
,你又不像重构它变成Future
函数,你可以使用这种方式帮你处理
use tokio::task;
fn blocking_task() -> u32 {
// 阻塞操作的逻辑
42
}
#[tokio::main]
async fn main() {
let handle = tokio::task::spawn_blocking(blocking_task);
let result = handle.await.unwrap();
println!("Blocking task result: {}", result);
}
tokio::io
这是一个模块,提供了异步I/O操作的功能。它包含了各种与I/O相关的类型和trait,例如AsyncRead
和AsyncWrite
,它们是用于异步读取和写入数据的trait。tokio::io
还提供了许多实用函数和方法,用于处理异步I/O操作。用户和std::io
一致。
use std::io;
use tokio::fs::File;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> io::Result<()> {
let mut file = File::create("file.txt").await?;
let data = b"Hello, World!";
file.write_all(data).await?;
let mut buffer = Vec::new();
file.read_to_end(&mut buffer).await?;
println!("Read from file: {:?}", buffer);
Ok(())
}
4. tokio::net
这是一个模块,提供了与网络编程相关的功能。它包含了异步TCP和UDP套接字的类型和函数,用于异步地进行网络通信。您可以使用这些类型和函数来构建异步的网络服务器或客户端。
use std::error::Error;
use tokio::net::TcpListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let listener = TcpListener::bind("127.0.0.1:8080").await?;
// 异步http服务器
loop {
let (mut socket, _) = listener.accept().await?;
tokio::spawn(async move {
let mut buffer = [0; 1024];
socket.read(&mut buffer).await?;
let response = b"HTTP/1.1 200 OK\r\n\r\nHello, World!";
socket.write_all(response).await?;
Ok::<(), Box<dyn Error>>(())
});
}
}
5. tokio::time
这是一个模块,提供了与时间相关的功能。它包含了异步的定时器和延迟功能,允许您在指定的时间间隔内执行代码或在一段时间后执行代码。
use std::time::Duration;
use tokio::time;
#[tokio::main]
async fn main() {
println!("Start");
time::sleep(Duration::from_secs(2)).await;
println!("Delay complete");
}
6. tokio::sync
这是一个模块,提供了与并发和同步相关的功能。它包含了各种异步原语,如Mutex
、RwLock
、Barrier
等,用于在异步环境中实现线程安全和同步。
以(Mutex)为例子
use std::sync::Arc;
use tokio::sync::Mutex;
#[tokio::main]
async fn main() {
let counter = Arc::new(Mutex::new(0));
for _ in 0..10 {
let counter = Arc::clone(&counter);
tokio::spawn(async move {
let mut value = counter.lock().await;
*value += 1;
});
}
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
let value = counter.lock().await;
println!("Counter value: {}", *value);
}
Tokio的异常处理
只要返回一个Result即可,如果你想异常是系统Error可以直接使用std::error::Error
async fn async_task() -> Result<(), Box<dyn std::error::Error>> {
// Async task logic
Ok(())
}
注意事项与建议
我们日常开发过程中可能会创建很多异步任务,这些异步任务都需要您调用await
时,控制流会暂时挂起当前的异步函数,并将控制权返回给Tokio运行时。Tokio会利用事件循环和调度器来决定下一个要执行的任务,并在适当的时机恢复挂起的任务的执行。
如果这个时候你想取消一个任务要怎么操作呢?
您可以调用handle.abort()
来发送取消信号给任务。如果异步任务已经完成,再对该任务执行abort()
操作将没有任何效果。也就是说,没有JoinError,task.await.unwrap_err()
将报错,而task.await.unwrap()
则正常。
use tokio::{self, runtime::Runtime, time};
fn main() {
let rt = Runtime::new().unwrap();
rt.block_on(async {
let task = tokio::task::spawn(async {
time::sleep(time::Duration::from_secs(10)).await;
});
// 让上面的异步任务跑起来
time::sleep(time::Duration::from_millis(1)).await;
task.abort(); // 取消任务
// 取消任务之后,可以取得JoinError
let abort_err: JoinError = task.await.unwrap_err();
println!("{}", abort_err.is_cancelled());
// 一般我推荐这种写法,兼容性强
if let Err(e) = task.await {
if e.is_cancelled() {
println!("Task was cancelled");
} else {
println!("Task encountered an error: {:?}", e);
}
}
})
}
如果您对Rust异步编程感兴趣,欢迎关注我的公众号【梦兽编程】,一起探索Rust的奥秘。如果您觉得这篇文章对您有帮助,请分享给更多需要的朋友。您的转发是我最大的动力!