rust 如何定义从类派生的时雄任务的生存期?

zdwk9cvp  于 2023-02-16  发布在  其他
关注(0)|答案(2)|浏览(188)

我正在尝试编写一个通用的set_interval函数助手:

pub fn set_interval<F, Fut>(mut f: F, dur: Duration)
where
    F: Send + 'static + FnMut() -> Fut,
    Fut: Future<Output = ()> + Send + 'static,
{
    let mut interval = tokio::time::interval(dur);

    tokio::spawn(async move {
        // first tick is at 0ms
        interval.tick().await;
        loop {
            interval.tick().await;
            tokio::spawn(f());
        }
    });
}

在从类内部调用它之前,这可以正常工作:

fn main() {}

struct Foo {}

impl Foo {
    fn bar(&self) {
        set_interval(|| self.task(), Duration::from_millis(1000));
    }
    
    async fn task(&self) {
        
    }
}

self不是'static,由于tokio::task,我们不能将生存期参数限制为小于'static的值。
是否可以修改set_interval实现,使其在这种情况下工作?
Link to playground
附言:试图

let instance = self.clone();
set_interval(move || instance.task(), Duration::from_millis(1000));

但我也得到一个错误:错误:捕获的变量无法转义FnMut闭包主体

b4lqfgs4

b4lqfgs41#

是否可以修改set_interval的实现,使其在这种情况下工作?
虽然spawn-ing f()也没有帮助,因为它排除了简单的“callback owns the object”解决方案(因为你需要callback和future来拥有对象,或者只需要future)。
我想这就剩下两个解决办法了:
1.将所有内容转换为共享可变性Arc,回调函数拥有一个Arc,然后在每次tick时克隆该Arc并将克隆对象移到未来(task方法)。
1.让future(task)从某个外部源获取对象,而不是在某个外部源上调用,这样中间回调就不需要做任何事情,或者回调可以完成获取并将其移到future,同样的diff。
顺便说一下,在这一点上,直接创建future,但允许克隆它是有意义的,所以set_interval将不接受回调,而是接受一个可克隆的future,它将spawn()克隆它存储的future,而不是重新创建它们。

nuypyhwy

nuypyhwy2#

正如@Masklinn所提到的,您可以克隆Arc来实现这一点。注意,克隆Arc * 不会 * 克隆底层数据,而只是指针,因此这样做通常是可以的,并且不会对性能产生重大影响。
下面是一个示例。下面的代码将生成错误async block may outlive the current function, but it borrows data, which is owned by the current function

fn main() {
    // 🛑 Error: async block may outlive the current function, but it borrows data, which is owned by the current function
    let data = Arc::new("Hello, World".to_string());
    tokio::task::spawn(async {
        println!("1: {}", data.len());
    });
    tokio::task::spawn(async {
        println!("2: {}", data.len());
    });
}

Rust建议将move添加到两个异步块,但这将导致借用错误,因为会有多个所有权。

要解决这个问题,我们可以为每个任务克隆Arc,* 然后 * 将move关键字添加到异步块:

fn main() {
    let data = Arc::new("Hello, World".to_string());

    let data_for_task_1 = data.clone();
    tokio::task::spawn(async move {
        println!("1: {}", data_for_task_1.len());
    });
    
    let data_for_task_2 = data.clone();
    tokio::task::spawn(async move {
        println!("2: {}", data_for_task_2.len());
    });
}

相关问题