Crate async_executors[−][src]
async_executors
Abstract over different executors.
The aim of async_executors is to provide a uniform interface to the main async executors available in Rust. We provide wrapper types that always implement the Spawn
and/or LocalSpawn
traits from futures, making it easy to pass any supported executor to an API which requires exec: impl Spawn
or exec: impl LocalSpawn
.
A problem with these traits is that they do not provide an API for getting a JoinHandle
to await completion of the task. The current trend in async is to favor joining tasks and thus certain executors now return JoinHandle
s from their spawn function. It’s also a fundamental building block for structured concurrency. Async_executors provides an executor agnostic JoinHandle
that wraps the framework native JoinHandle types.
SpawnHandle
traits are also provided so API’s can express you need to pass them an executor which allows spawning whilst returning a JoinHandle
. Note that this was already provided by the futures in SpawnExt::spawn_with_handle
, but this uses RemoteHandle
which incurs a performance overhead. By wrapping the native JoinHandle types, we can avoid some of that overhead while still being executor agnostic.
The traits provided by this crate are also implemented for the Instrumented
and WithDispatch
wrappers from tracing-futures when the tracing
feature is enabled. So you can pass an instrumented executor to an API requiring exec: impl SpawnHandle<SomeOutput>
.
The currently supported executors are (file an issue on GitHub if you want to see another one supported):
- async-global-executor - supports spawning
!Send
futures and works on Wasm. - async-std - supports spawning
!Send
futures and works on Wasm (uses async-global-executor and bindgen under the hood). - tokio CurrentThread -
tokio::runtime::Runtime
with basic scheduler and a LocalSet. (supports spawning!Send
futures) - tokio ThreadPool -
tokio::runtime::Runtime
with threadpool scheduler. - wasm-bindgen-futures (only available on Wasm)
- the futures-executor executors - They already implemented
Spawn
andSpawnLocal
, but we implement theSpawnHandle
family of traits for them as well. The typesThreadPool
,LocalPool
andLocalSpawner
are re-exported for convenience.
All executors are behind feature flags: async_std
, async_global
, tokio_ct
, tokio_tp
, bindgen
, localpool
, threadpool
.
Table of Contents
Install
With cargo add:
cargo add async_executors
With cargo yaml:
dependencies:
async_executors: ^0.4
With Cargo.toml
[dependencies]
async_executors = "0.4"
Upgrade
Please check out the changelog when upgrading.
Dependencies
This crate has few dependencies. Cargo will automatically handle it’s dependencies for you.
The only hard dependencies are futures-task
and futures-util
. The rest are the optional dependencies to turn on support for each executor.
Security
The crate itself uses #[ forbid(unsafe_code) ]
.
Our dependencies use unsafe.
Performance
Most wrappers are very thin but the Spawn
and LocalSpawn
traits do imply boxing the future. With executors boxing futures
to put them in a queue you probably get 2 heap allocations per spawn.
JoinHandle
uses the native JoinHandle
types from tokio and async-std to avoid the overhead from RemoteHandle
, but for async-std, wrap the future in Abortable
to create consistent behavior across all executors. The JoinHandle
provided cancels it’s future on drop unless you call detach
on it.
SpawnHandle
and LocalSpawnHandle
require boxing the future twice, just like Spawn
and LocalSpawn
.
Existing benchmarks for all executors can be found in executor_benchmarks.
Missing features
These are some features that aren’t provided yet but that are on the todo list:
- an agnostic timeout mechanism.
- an agnostic interface for
spawn_blocking
.
Usage
For API providers
When writing a library that needs to spawn, you probably shouldn’t lock your clients into one framework or another. It’s usually not appropriate to setup your own thread pool for spawning futures. It belongs to the application developer to decide where futures are spawned and it might not be appreciated if libraries bring in extra dependencies on a framework.
In order to get round this you can take an executor in as a parameter from client code and spawn your futures on the provided executor. Currently the only two traits that are kind of widely available for this are Spawn
and LocalSpawn
from the futures library. Unfortunately, other executor providers do not implement these traits. So by publishing an API that relies on these traits, you would have been restricting the clients to use the executors from futures, or start implementing their own wrappers that implement the traits.
Async_executors has wrappers providing impls on various executors, namely tokio, async_std, wasm_bindgen. As such you can just use the trait bounds and refer your users to this crate if they want to use any of the supported executors.
All wrappers also implement Clone
, Debug
and the zero sized ones also Copy
. You can express you will need to clone in your API: impl Spawn + Clone
.
Note that you should never use block_on
inside async contexts. Some backends we use like tokio and RemoteHandle
from futures use catch_unwind
, so try to keep futures unwind safe.
Spawning with handles
You can use the SpawnHandle
and LocalSpawnHandle
traits as bounds for obtaining join handles.
Example
use { async_executors :: { JoinHandle, SpawnHandle, SpawnHandleExt } , std :: { sync::Arc } , futures :: { FutureExt, executor::{ ThreadPool, block_on } } , }; // Example of a library function that needs an executor. Just use impl Trait. // fn needs_exec( exec: impl SpawnHandle<()> ) { let handle = exec.spawn_handle( async {} ); } // A type that needs to hold on to an executor during it's lifetime. Here it // must be heap allocated. // struct SomeObj{ exec: Arc< dyn SpawnHandle<u8> > } impl SomeObj { pub fn new( exec: Arc< dyn SpawnHandle<u8> > ) -> SomeObj { SomeObj{ exec } } fn run( &self ) -> JoinHandle<u8> { self.exec.spawn_handle( async{ 5 } ).expect( "spawn" ) } } fn main() { let exec = ThreadPool::new().expect( "build threadpool" ); let obj = SomeObj::new( Arc::new(exec) ); let x = block_on( obj.run() ); assert_eq!( x, 5 ); }
For API consumers
You can basically pass the wrapper types provided in async_executors to API’s that take any of the following. Traits are also implemented for Rc
, Arc
, &
, &mut
, Box
and Instrumented
and WithDispatch
from tracing-futures wrappers:
impl Spawn
impl LocalSpawn
impl SpawnHandle<T>
impl LocalSpawnHandle<T>
All wrappers also implement Clone
, Debug
and the zero sized ones also Copy
.
Some executors are a bit special, so make sure to check the API docs for the one you intend to use. Some also provide extra methods like block_on
which will call a framework specific block_on
rather than the one from futures.
Example
use { async_executors :: { AsyncStd, TokioTpBuilder, SpawnHandle } , std :: { convert::TryFrom } , }; fn needs_exec( exec: impl SpawnHandle<()> + SpawnHandle<String> ){}; // AsyncStd is zero sized, so it's easy to instantiate. // needs_exec( AsyncStd ); let tp = TokioTpBuilder::new().build().expect( "build threadpool" ); needs_exec( tp );
For more examples, check out the examples directory. If you want to get a more polished API for adhering to structured concurrency, check out async_nursery.
API
API documentation can be found on docs.rs.
Contributing
Please check out the contribution guidelines.
Testing
Run ci/test.bash
and ci/wasm.bash
to run all tests.
Code of conduct
Any of the behaviors described in point 4 “Unacceptable Behavior” of the Citizens Code of Conduct are not welcome here and might get you banned. If anyone including maintainers and moderators of the project fail to respect these/your limits, you are entitled to call them out.
License
Structs
AsyncGlobal | async_global An executor that spawns tasks on async-global-executor. In contrast to the other executors, this one is not self contained, because async-global-executor does not provide an API that allows that, so the threadpool is global. |
AsyncStd | async_std An executor that spawns tasks on async-std. In contrast to the other executors, this one is not self contained, because async-std does not provide an API that allows that, so the threadpool is global. |
Bindgen | bindgen and WebAssemblyA type that implements |
JoinHandle | A framework agnostic JoinHandle type. Cancels the future on dropping the handle.
You can call |
LocalPool | A single-threaded task pool for polling futures to completion. |
LocalSpawner | |
ThreadPool | A general-purpose thread pool for scheduling tasks that poll futures to completion. |
TokioCt | tokio_ct An executor that uses a |
TokioCtBuilder | Builder to create a |
TokioTp | tokio_tp An executor that uses tokio::runtime::Runtime. |
TokioTpBuilder | Builder to create a |
Traits
LocalSpawnHandle | This is similar to |
LocalSpawnHandleExt | Let’s you spawn a !Send future and get a JoinHandle to await the output of a future. |
SpawnHandle | Let’s you spawn and get a JoinHandle to await the output of a future. |
SpawnHandleExt | Convenience trait for passing in a generic future to |