Tokio: spawn vs spawn_local

If I create a top-level future inside which I spawn some tasks, those tasks continue to execute even after the call to block_on that drives the top-level future returns:

use tokio::runtime::Runtime;
use tokio::spawn;
use tokio::time::sleep;

use std::time::Duration;

fn main() {
    let rt = Runtime::new().unwrap();

    rt.block_on(async {
        spawn(async {
            loop {
                println!("a");
                sleep(Duration::from_secs(2)).await;
            }
        });
    });

    println!("done");

    loop {}
}

However tasks spawned using spawn_local seem to stop executing right after block_on returns:

use tokio::runtime::Runtime;
use tokio::task::{spawn_local, LocalSet};
use tokio::time::sleep;

use std::time::Duration;

fn main() {
    let rt = Runtime::new().unwrap();
    let local = LocalSet::new();

    local.block_on(&rt, async {
        spawn_local(async {
            loop {
                println!("a");
                sleep(Duration::from_secs(2)).await;
            }
        });
    });

    println!("done");

    loop {}
}

And the same thing happens with run_until:

use tokio::runtime::Runtime;
use tokio::task::{spawn_local, LocalSet};
use tokio::time::sleep;

use std::time::Duration;

fn main() {
    let rt = Runtime::new().unwrap();
    let local = LocalSet::new();

    rt.block_on(local.run_until(async {
        spawn_local(async {
            loop {
                println!("a");
                sleep(Duration::from_secs(2)).await;
            }
        });
    }));

    println!("done");

    loop {}
}

By the way, async-std seems to behave like tokio in this respect.

So what's the reason for this difference between spawn and spawn_local?

This is because a LocalSet behaves like a current_thread runtime in this regard. See the following quote from the Tokio tutorial:

Because the current_thread runtime does not spawn threads, it only operates when block_on is called. Once block_on returns, all spawned tasks on that runtime will freeze until you call block_on again. Use the multi_threaded runtime if spawned tasks must keep running when not calling block_on .

Bridging with sync code

One way to see the issue is to consider the question "where should the local task run?". It certainly can't run on any other thread than your main thread, as that would be in violation with the task not being Send, and the main thread is currently occupied on looping really quickly.

2 Likes

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.