Tokio: `Handle` of a `new_current_thread` runtime used in a different thread

Recently I encountered a piece of code in my workplace, which can be minimalized like the following.

async fn work(x: &str) {
    tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
    println!("{}: work {} done", std::thread::current().name().unwrap(), x);
}
fn main() {
    let rt = tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap();
    rt.block_on(async {
        let handle = tokio::runtime::Handle::current();
        let child = std::thread::Builder::new().name("child".to_string()).spawn(move || handle.block_on(work("inner"))).unwrap();
        work("outer").await;
        let _ = child.join();
    });
} 

This code could run with no problem, but I don't understand why it works.

According to tokio::runtime - Rust

The current-thread scheduler provides a single-threaded future executor. All tasks will be created and executed on the current thread

The supposedly single threaded runtime is used in a different thread through its handle. What is the expected behavior ?
Running the code shows the "inner" work runs on child thread. Doesn't this contradict All tasks will be created and executed on the current thread ?

No. The task is (probably) created on the main thread. But you are creating a new thread. Tokio can't actively prevent you from calling into std and spawning a new thread manually, after all.

it is expected behavior. if you check the document for Handle::block_on, it says:

This runs the given future on the current thread, blocking until it is complete, and yielding its resolved result. Any tasks or timers which the future spawns internally will be executed on the runtime.

When this is used on a current_thread runtime, only the Runtime::block_on method can drive the IO and timer drivers, but the Handle::block_on method cannot drive them. This means that, when using this method on a current_thread runtime, anything that relies on IO or timers will not work unless there is another thread currently calling Runtime::block_on on the same runtime.

try this:

fn main() {
	let rt = tokio::runtime::Builder::new_current_thread()
		.enable_all()
		.build()
		.unwrap();
	rt.block_on(async {
		let handle = tokio::runtime::Handle::current();
		let child = std::thread::Builder::new()
			.name("child".to_string())
			.spawn(move || {
				handle.block_on(async {
					handle.spawn(work("inner1"));
					work("inner2").await;
				})
			})
			.unwrap();
		work("outer").await;
		let _ = child.join();
	});
	// give scheduler some time before exit
	rt.block_on(work("final"));
}

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.