doplumi
doplumi

Reputation: 3118

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

Re-building setInterval in JS:

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;
}

This works, but I want it to be .awaitable until the first execution ends.

I.e. Instead of doing:

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

I want to:

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

Note that this is different behaviour from above in that it runs the do_something task immediately, but it's intended.

My solution:

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;
    });
}

This works, but I have some doubts

  1. Is it a waste of resources to task::spawn twice here? Any way it can be done only once?
  2. Is it a mistake to discard the outer task::spawn as soon as the function ends? Will it keep getting run indefinitely, even though it's not tracked anymore, just fired and forgotten?

Upvotes: 0

Views: 1205

Answers (1)

Caesar
Caesar

Reputation: 8534

To answer your two questions:

  1. Don't worry about spawning a task too many: On your average hardware, you should be able to spawn a few 100,000 of them per second. It is unnecessary in this case, though, because:
  2. Tasks get "detached" if they are dropped, so the futures inside them continue to be polled by your runtime. This also means that you don't need forever.await;

The following seems to work:

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

Upvotes: 4

Related Questions