async-std
async-std copied to clipboard
[tracking] spawning non-Send tasks
In https://github.com/async-rs/async-std/pull/251 we discussed the option of adding a task::spawn_local method, similar to @alexcrichton's design of spawn_local in wasm-bindgen-futures. This is a tracking issue for that design to get feedback on whether people would want this, and how we would go about implementing this.
I'd imagine we would introduce a new API; task::spawn_local which takes a non-Send Future, and ensures it's polled to completion on the current thread. This would interact seamlessly with task::yield_now out of the box.
Example
use async_std::task;
task::spawn(async move {
task::sleep(Duration::from_secs(1)).await;
// The async code in this block will never be moved between threads.
task::spawn_local(async {
task::sleep(Duration::from_secs(1)).await;
}).await;
}).await;
Note: task::sleep does implement Send, but it was an easy example to use.
tokio 0.2.2 added spawn_local API. Their implementation is interesting: https://docs.rs/tokio/0.2.2/tokio/task/struct.LocalSet.html
Example:
use std::rc::Rc;
use tokio::task;
let unsend_data = Rc::new("my unsend data...");
let mut rt = Runtime::new().unwrap();
// Construct a local task set that can run `!Send` futures.
let local = task::LocalSet::new();
// Run the local task group.
local.block_on(&mut rt, async move {
let unsend_data = unsend_data.clone();
// `spawn_local` ensures that the future is spawned on the local
// task group.
task::spawn_local(async move {
println!("{}", unsend_data);
// ...
}).await.unwrap();
});
I'm not sure if I like that API. That seems to push the task group into a global? What happens when two local groups nest?
I didn't experiment a lot with it, but as I understood you can not have nested groups, because a group is scheduled by block_on.
I'd like to put in a vote for this. As I said on Discord:
Aside from being handy for non-Send futures, I found it helpful in writing an explanation of how asynchronous programming works, because it lets me show the single-threaded case before introducing thread pools. The point of async is to let threads not get pinned down by blocking I/O, and it's easier to make a clear demonstration of how that works without having threads complicate the picture. The reader can see how one thread's resources are now serving any number of different async tasks. Then the thread pool can be presented as an optimization. "Oh, by the way, you can also just spawn stuff onto a thread pool. This is mostly how it's done."
There is now unstable function spawn_local(): https://docs.rs/async-std/1.6.2/async_std/task/fn.spawn_local.html
It is unfortunately not marked as unstable in docs because this:
#[cfg(feature = "default")]
pub use spawn_local::spawn_local;
needs to be changed to:
#[cfg(feature = "default")]
#[cfg_attr(feature = "docs", doc(cfg(unstable)))]
pub use spawn_local::spawn_local;
Right - I'm saying, I would be delighted if this were stabilized. :)