从浅到深-Tokio强大的Rust异步框架,常用的Api与实战案例

2 阅读5分钟

从浅到深-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);
}
  1. tokio::io 这是一个模块,提供了异步I/O操作的功能。它包含了各种与I/O相关的类型和trait,例如AsyncReadAsyncWrite,它们是用于异步读取和写入数据的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

这是一个模块,提供了与并发和同步相关的功能。它包含了各种异步原语,如MutexRwLockBarrier等,用于在异步环境中实现线程安全和同步。

以(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的奥秘。如果您觉得这篇文章对您有帮助,请分享给更多需要的朋友。您的转发是我最大的动力!