Best practice for constraining an `async` block to a single thread when using Tokio?

52 points buywall

Hey  🦀s,

Is there a best practice for forcing an async block to be run on a single system thread? (C.f. this old GitHub thread.)

I'm using CUDA from Rust, and every new thread I create has to be registered with CUDA. Thus I want to run async blocks that look like this:

async move {
  register_thread();
  // Do CUDA stuff
}

Thoughts:

  1. Naively running this with tokio::spawn doesn't work because tokio might move my block between system threads at any yield point inside my block.
  2. LocalSet doesn't work (for me) because it's not Send and thus my control code can't use tokio::spawn. There's a workaround described in the docs but I think it's too complicated.
  3. block_in_place doesn't work (I think) because to run async code you need to call Handle::current().block_on which will hand the async block to the the original tokio runtime which may move it between system threads (IIUC).

So, my final solution is to just create a new single-threaded runtime inside a new system thread, like this:

std::thread::spawn({
  move || {
    tokio::runtime::Builder::new_current_thread()
      .enable_all()
      .build()
      .unwrap()
      .block_on(async move {
        register_thread();
        // Do CUDA stuff
      });
  }
});

Obviously this feels inelegant - I don't think I should be creating multiple tokio runtimes.

Anyone know the correct way to do this?

EDIT: I ended up using this solution:

tokio::task::spawn_blocking({
  move || {
    tokio::runtime::Handle::current().block_on(async move {
      register_thread();
      // Do CUDA stuff
    })
  }
});