What async executors provide a way to execute once without blocking the caller?

Hello.

My goal is to execute tasks asynchronously, on the same thread, without blocking the caller. That is, without waiting for the completion of the tasks.

Here is a simplified structure of the program:

fn get_executor() -> &SomeExecutor {
    // get some global executor
}

fn somewhere_in_the_code() {
    let executor = get_executor();

    executor.spawn(async {
        loop {
            do_something();
            async_sleep(1);
        }
    });
}

fn my_function() {
    let executor = get_executor();

    executor.execute_once();
}

fn main() {
    loop {
        my_function();
    }
}

I can not modify the main function. It ocasionally calls my_function, and during these calls I need to execute tasks once, without blocking the main thread.

Are there some lightweight executors that allow execution of the tasks in the same thread and execution without waiting for completion of the tasks?

Thanks!

You can spawn a Tokio current-thread runtime on a background thread and spawn to that.

use std::sync::OnceLock;
use tokio::runtime::{Builder, Handle};

fn init_runtime() -> Handle {
    let rt = Builder::new_current_thread()
        .enable_all()
        .build()
        .unwrap();
    
    let handle = rt.handle().clone();
    std::thread::spawn(move || {
        // This never returns. Futures you spawn will run inside this call.
        rt.block_on(std::future::pending::<()>());
    });
    
    handle
}

static RT: OnceLock<Handle> = OnceLock::new();

fn get_executor() -> &'static Handle {
    RT.get_or_init(init_runtime)
}

fn somewhere_in_the_code() {
    let executor = get_executor();

    executor.spawn(async {
        loop {
            do_something().await;
            tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
        }
    });
}

You said that you wanted the background tasks to get executed on the main thread, but that's not possible unless you have a runtime on the main thread, which would require modifying main.

2 Likes

Thank you for your response.

My task requires me to execute the tasks on the same platform thread the my_function function is called from. So I don't need to spawn a new thread and probably there is no way I use block_on function anywhere (either it will block the main thread, or it will not execute on the main thread, which is the requirement).

Is there a way to lazily initialize an executor and async runtime inside the my_function function and then execute it once per my_function call, without blocking and waiting for the tasks completion?

You could store a async_executor::LocalExecutor in a thread_local variable, spawn tasks on it and .try_tick() it (possibly multiple times until it returns false) in my_function:

use async_executor::LocalExecutor;

thread_local!(static EXECUTOR: LocalExecutor<'static> = LocalExecutor::new());

fn somewhere_in_the_code() {
    EXECUTOR.with(|executor| {
        executor
            .spawn(async {
                loop {
                    do_something().await;
                    async_sleep(1).await;
                }
            })
            .detach()
    });
}

fn my_function() {
    // Tick the executor until no task is ready.
    // You may alternatively want to `.try_tick()` it only once
    // or a maximum fixed amount of times.
    EXECUTOR.with(|executor| while executor.try_tick() {})
}

fn main() {
    loop {
        my_function();
    }
}
3 Likes

Thank you! That is exactly what I needed!

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.