Rust 异步取消

Develon / 2023-05-15 / 原文

(&mut join_handle).await

JoinHandle 支持这种操作,从而不消耗 JoinHandle ,参考:https://github.com/tokio-rs/tokio/discussions/4019

通过 spawn task 实现超时取消

timer.rs

use std::future::Future;

pub async fn sleep(n: u64) {
    tokio::time::sleep(std::time::Duration::from_millis(n)).await;
}

pub async fn timeout<T>(task: impl Future<Output = T> + Send + 'static, millis: u64) -> Option<T>
where
    T: Send + 'static,
{
    let mut join_handle = tokio::spawn(task);
    tokio::select! {
        Ok(v) = &mut join_handle => Some(v),
        () = sleep(millis) => {
            join_handle.abort();
            None
        },
    }
}

测试

mod timer;
use timer::{sleep, timeout};

#[derive(Debug)]
struct A;
impl Drop for A {
    fn drop(&mut self) {
        println!("drop A, 任务结束或被取消")
    }
}

#[tokio::main]
async fn main() {
    match timeout(
        async {
            let _a = A;
            println!("开始");
            sleep(5000).await; // 模拟任务耗时,5秒
            println!("完成");
            12345
        },
        2000, // 超时时间,2秒
    )
    .await
    {
        Some(v) => {
            println!("结果:{v}");
        }
        None => {
            println!("超时");
        }
    }
    println!("程序运行结束");
}

运行结果:

开始
drop A, 任务结束或被取消
超时
程序运行结束

我们增加超时时间大于5秒,结果:

开始
完成
drop A, 任务结束或被取消
结果:12345
程序运行结束

能否不使用 spawn

使用 tokio 官方功能吧!

#[derive(Debug)]
struct A;
impl Drop for A {
    fn drop(&mut self) {
        println!("drop A, 任务结束或被取消")
    }
}

#[tokio::main]
async fn main() {
    match tokio::time::timeout(std::time::Duration::from_millis(5000), async {
        let _a = A;
        println!("开始");
        std::future::pending::<()>().await; // 永远不会被解决的未来
        println!("完成");
        12345
    }).await {
        Ok(v) => {
            println!("结果:{v}");
        }
        Err(_) => {
            println!("超时");
        }
    }
    println!("程序运行结束");
}

结果:

开始
drop A, 任务结束或被取消
超时
程序运行结束

see

https://blog.yoshuawuyts.com/async-cancellation-1/