如何产生重复任务并等待其首次执行?

How to spawn a repeating task and await its first execution?

在 JS 中重新构建 setInterval

pub async fn set_interval<T, F>(interval: Duration, do_something: T)
where
    T: (Fn() -> F) + Send + Sync + 'static,
    F: Future + Send,
{
    let forever = task::spawn(async move {
        let mut interval = time::interval(interval);

        loop {
            interval.tick().await;
            do_something().await;
        }
    });

    forever.await;
}

这行得通,但我希望它 .await 能够直到第一次执行结束。

即而不是做:

do_something().await
set_interval(Duration::from_secs(1), do_something)

我想:

set_interval(Duration::from_secs(1), do_something).await

请注意,这与上面的行为不同,因为它会立即 运行 执行 do_something 任务,但这是有意为之的。

我的解决方案:

pub async fn set_interval<T, F>(interval: Duration, do_something: T)
where
    T: (Fn() -> F) + Send + Sync + 'static,
    F: Future + Send,
{
    do_something().await;
    
    task::spawn(async move {
        let forever = task::spawn(async move {
            let mut interval = time::interval(interval);

            loop {
                interval.tick().await;
                do_something().await;
            }
        });

        forever.await;
    });
}

这行得通,但我有一些疑问

  1. 在这里task::spawn两次是不是浪费资源?有什么办法只能做一次吗?
  2. 函数一结束就丢弃外面的task::spawn是不是错误?它是否会无限期地获得 运行,即使它不再被跟踪,只是被解雇并被遗忘?

回答你的两个问题:

  1. 不必担心产生太多任务:在您的平均硬件上,您应该能够每秒产生 100,000 个任务。但是,在这种情况下是不必要的,因为:
  2. 如果任务被删除,它们就会“分离”,因此它们内部的未来将继续由您的运行时轮询。这也意味着您不需要 forever.await;

以下似乎有效:

pub async fn set_interval<T, F>(interval: std::time::Duration, do_something: T)
where
    T: (Fn() -> F) + Send + Sync + 'static,
    F: std::future::Future + Send,
{
    // The interval time alignment is decided at construction time. 
    // For all calls to be evenly spaced, the interval must be constructed first.
    let mut interval = tokio::time::interval(interval);
    // The first tick happens without delay.
    // Whether to tick before the first do_something or after doesn't matter.
    interval.tick().await;
    
    do_something().await;
    
    tokio::task::spawn(async move {
        loop {
            interval.tick().await;
            do_something().await;
        }
    });
}

Playground