How to run a thread in background?

use futures::future::{BoxFuture, Future, join_all};
use tokio::sync::Mutex;
use tokio::time::{self, Duration};
#[macro_use]
extern crate lazy_static;
use std::sync::Arc;



async fn a_job() {
    std::thread::sleep(Duration::from_secs(1));
    println!("I am working with a_job!");
}

async fn b_job() {
    std::thread::sleep(Duration::from_secs(10));
    println!("I am working with b_job!");
}

struct Job<R> {
    func: Box<dyn Fn() -> BoxFuture<'static, R> + Send + Sync + 'static>,
}

impl<R> Job<R> {
    fn new<F>(f: fn() -> F) -> Job<F::Output>
    where
        F: Future<Output = R> + Send + 'static,
    {
        Job {
            func: Box::new(move || Box::pin(f())),
        }
    }
    async fn run(&self) -> R {
        (self.func)().await
    }
}

lazy_static! {
    static ref GLOBAL: Arc<Mutex<Vec<Job<()>>>> = Arc::new(Mutex::new(vec![Job::new(a_job), Job::new(b_job)]));
}

#[tokio::main]
async fn main() {
    let recv_trigger = tokio::spawn(async move {
        let mut interval = time::interval(time::Duration::from_secs(1));
        loop {
            tokio::select! {
               _ = interval.tick() => {
                let global = GLOBAL.clone();
                let mut v = global.lock().await;
                let mut tasks = Vec::new();
                for job in v.iter_mut(){
                    tasks.push(job.run()); 
                }
                join_all(tasks).await;
               }
            }
        }
    });

    let _ = tokio::join!(recv_trigger);
}

I encounter a problem, when the interval.tick() is true, the b_job and a_job wait 10 seconds synchronously, but I want b_job and a_job to wait in background, and print:

I am working with a_job!
I am working with a_job!
I am working with a_job!
I am working with a_job!
I am working with a_job!
I am working with a_job!
I am working with a_job!
I am working with a_job!
I am working with a_job!
I am working with a_job!
I am working with b_job!
I am working with a_job!
.....

it just is similar to go keyword in golang, so how to implement it? :joy:

If you use join_all on futures, they are executed in a sort of “cooperative multitasking” manner on the same thread. Multi-threading with async code execution happens, too, on a per-task basis when you use tokio::spawn to spawn tasks; even then the parallelization is limited to a fixed set of worker thread. In either case, you need to ensure that no task is unnecessarily blocking the execution, that’s the “cooperative” part of “cooperative multitasking”: bad players can starve the whole system.

Now, std::thread::sleep is a blocking function call; a “bad player” so to speak. Or put differently: You’re using a synchronous sleep primitive (thread::sleep) in an asynchronous environment (async fns, executed on tokio’s executor). To fix the problem, you’ll need to replace

std::thread::sleep(Duration::from_secs(10));

with

tokio::time::sleep(Duration::from_secs(10)).await;

(you might need to enable the time feature of tokio in your Cargo.toml)


Edit: For further background, this is a cool explanation I’ve found online.

1 Like

Sorry, I didn't mean that :joy:, I mean, is there any way to run task and wait in background? When the interval is activated, a_job and b_job run and wait task in background, after a one-second delay,interval is activated again without waiting for threads spawned by a_job and b_job finish, it is just like golang: go a_job().

As mentioned, you can run tasks in the background with tokio::spawn. Your functions a_job and b_job are terrible functions because they’re async fn, yet block execution synchronously for multiple seconds. If these things are stand-ins for something else, there’s three possibilites I can come up with: Either they are computation-intensive jobs, in which case they should not be async fns, and tokio might be the wrong framework for executing those; but you could try using tokio::spawn_blocking in order to execute them in a large pool of allowed-to-be-blocking worker threads; or they’re handling some form of IO synchronously, in which case you should consider making them asynchronous instead (in this example the mentioned transition from std::tread to tokio::time) or you could turn them into non-async functions and use spawn_blocking as-well (usually less efficient and less scalable), or they’re already asynchronous tasks, (they aren’t in your example where they use thread::sleep!), in which case you can use tokio::spawn to start them in the background. With tokio::spawn, you don’t need to keep and await the returned handle, if you’re not interested in observing the result/completion of the spawned task. You can just drop the handle and it will keep running in the background.

1 Like

I am sorry, I got it wrong, thank you!

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.