How to call a closure with a delay in Rust? Is tokio :: timer :: Delay a proper tool for making asynchronous callbacks?

How to call a closure with a delay in Rust? Is tokio::timer::Delay a proper tool for making asynchronous callbacks?

My code:


pub struct Object1
{
  k1 : i32,
}

//

impl Object1
{
  pub fn new() -> Object1
  {
    Object1
    {
      k1 : 13
    }
  }
}

//

fn main()
{

  let object1 : Box<Object1> = Box::new( Object1::new() );

  callback_call_1( Box::new( ||
  {
    println!( "{:?}", object1.k1 );
  }));

}

//

fn callback_call_1<F>( mut callback1 : F )
where
  F : FnMut()
{
  println!( "inside callback_call_1" );
  callback1();
  /* how to call callback1 with delay 1 second, not blocking the thread?
  is it possible to call the callback1 in the same thread?
  is it possible to reach similar behavior setTimeout from JavaScript has?
  */
}

Here is playground.
Please help if you know the answer or give a hint where to look for the solution of the problem.

The fact that you are talking about tokio::timer::Delay reveals to me that you are using Tokio version 0.1.x, which is extremely old. I would encourage you to upgrade. Here's how you do it with latest Tokio:

tokio::time::sleep(duration).await;
callback1().await;

To call it repeatedly on an interval, use a loop:

let mut interval = tokio::time::interval(duration);
loop {
    interval.tick().await;
    callback1().await;
}
3 Likes

Thanks Alice. No, actually I don't use tokio. I am trying to find a proper solution of delayed call of a closure. What should I read?

Basically, you need to think about who should execute the delayed call. (Rust is a low-level language so you're not exempted from thinking about these details.) Delayed calls introduce concurrency: by the time the callback needs to be executed, there may be other activities going in your program (somewhat simplified).

Your best choice actually is to use an engine like tokio. Timers are surprisingly difficult-to-implement things, tokio provides a scalable timer wheel implementation that efficiently supports many simultaneous timers.

If you don't want to use tokio, you need to somehow arrange for the timer to go off. A simple approach for infrequent timers is to spawn a thread, let the thread sleep for the delay, then do whatever you want to do.

In both cases will this make your code multi-threaded so you're entering the world of Arc, Mutex, and/or message passing etc. if your closure needs to share anything with the outside world.

You want to read the chapters on threading in the Rust book and/or the Tokio tutorial.

ps: and there are probably crates out there as well that implement the mechanics of maintaining timers and executing them concurrently.

1 Like

Thank you, Godmar. What about event loop mechanism similar to what JS have. Is such mechanism available out of the box for Rust? Can you recommend a crate solving the problem if there is no such thing in the standard library?

That's a good question I've been meaning to ask Alice as well.

Is there a single-threaded async executor framework for Rust (where you stay in single-threaded Rust, e.g. RefCell, Rc, etc.?) I think you have some control about the number of threads Tokio uses but I don't know if there's a way to avoid the overhead (Arc, etc.) of using primitives that are thread-safe.

1 Like

Essentially, that's what the executor like Tokio will do. The details are not the same, but the semantics are similar.

Looks like the Tokio runtime with current-thread sheduler can be the thing you want?

2 Likes

Tokio is the primary library that provides such an event loop. In fact, as far as I am aware, Deno uses Tokio for exactly this purpose, and Deno is a Javascript runtime implemented in Rust.

If you want to do it without Tokio, you could spawn an ordinary thread with std::thread::spawn and do the same sleep.

@godmar If you wish to avoid synchronization overhead, you can combine Tokio's current thread runtime with the LocalSet type. Actix-web does this.

1 Like

Thank you. That's so interesting!
Any example of delayed call of a callback with Tokio?

There's an example in my first reply to this topic. If you need it to run in the background, wrap it in tokio::spawn.

Alice, as I understand it blocks the thread? No?

No. If you check out my article on blocking the thread, you will see that there's an important difference between std::thread::sleep and tokio::time::sleep. Only the first blocks the thread.

1 Like

To be clear, this provides a single-threaded/sequentially consistent environment for the tasks in a LocalSet, but it doesn't make tokio itself single-threaded/avoid synchronization? And it's otherwise orthogonal, right, so all async I/O etc. works as before?

1 Like

Well the internals of the LocalSet also avoid synchronization where possible. Though if you want to talk about LocalSet, we should probably start a new topic.

2 Likes

Good question.

Thank you, Alice. Article is fantastic! I am going to read it.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.