When using on_block() in thread::spawn, error said: there is no reactor running, must be called from the context of a Tokio 1.x runtime

async fn query(page: i32) -> String{
    let client = reqwest::Client::new();
    let url = "http://abc.com";
    let body = client.get(&url)
            .header("DEVICE-TYPE", "android")
            .send().await.unwrap().text().await.unwrap();
    dbg!(&body);
    body
}
#[tokio::main]
async fn main() {

    for x in 0..10 {
        let partial_result = thread::spawn(move||{
            let body = block_on(query(y));
        });
    }
    loop {}
}

cargo.toml


[dependencies]
reqwest = { version = "0.11", features = ["blocking", "json"] }
tokio = { version = "0.2", features = ["full"] }
serde_json = "1.0"
futures = "0.3.30"
async-std = { version = "1", features = ["attributes", "tokio1"] }

error:

there is no reactor running, must be called from the context of a Tokio 1.x runtime

reqwest must be executed from a tokio runtime. You don't execute query(y) in a tokio runtime but spawn a new thread which then uses some other executor (I assume futures') to poll the future.

1 Like

Thanks for your reply.

Im more clear now.

But how to fix the code?

All i want to do is spawn a bunch of threads to do the data fetching.

After all the thread done fetching then merge all the data together

You'd do it solely in the tokio runtime. #[tokio::main] already creates a multithreaded runtime for you, so you have a bunch of worker threads (defaults to the amount of (virtual?) cores on your system) already at your disposal. No need to spawn additional threads not managed by tokio. All you need to do is spawn tasks onto the worker threads and let them do the fetching. Here an example using JoinSet to make your requests execute in parallel:

#[tokio::main(worker_threads = 10)]
async fn main() {
    let mut set = JoinSet::new();
    
    for x in 0..10 {
        set.spawn(async move { query(x).await });
    }
    
    while let Some(q) = set.join_next().await {
        let res = q.unwrap();
        println!("{res}");
    }
}

Playground.

1 Like

The correct way to sleep forever in async code is std::future::pending::<()>().await. A loop will consume 100% cpu.

1 Like

i found out that i can achieve that with use futures::future::{join_all};

let a = join_all(future_vec).await

are there any fundanmental difference to your solution?

sorry for the naive question, im new for rust

join_all is ordered, JoinSet isn't. Be sure that you spawn you query invocation onto the worker threads with tokio::task::spawn, rather than putting them in your future_vec directly, or else they will all run concurrently on the main thread and not in parallel on the worker threads.

1 Like

Spawning is usually more efficient than join_all. With spawning, the runtime can manage each individual task separately, whereas it has less control over them with join_all.

1 Like