18 个版本
0.7.0 | 2023 年 7 月 22 日 |
---|---|
0.6.0 | 2022 年 4 月 24 日 |
0.5.1 | 2022 年 1 月 6 日 |
0.4.2 | 2021 年 6 月 10 日 |
0.0.0 | 2019 年 10 月 28 日 |
#432 在 异步
9,272 每月下载量
用于 51 个 crate (12 个直接)
95KB
1.5K SLoC
async_executors
抽象不同的执行器。
async_executors 的目标是帮助您编写与执行器无关的库。我们通过 traits 表达常见的执行器功能,并为最常用的执行器实现它。这样,库可以根据需要要求确切的功能,而客户端代码可以使用它们选择的任何执行器,只要它可以提供所需的功能。
可用的 traits 被分组在 iface
模块中。我们还实现了来自 futures 的 Spawn
和/或 LocalSpawn
traits。
所有支持的执行器都通过 features 开启,见下文。
目录
安装
使用 cargo add: cargo add async_executors
使用 cargo yaml
dependencies:
async_executors: ^0.7
使用 Cargo.toml
[dependencies]
async_executors = "0.7"
升级
升级时请查看 变更日志。
依赖项
此 crate 有少量依赖项。Cargo 将自动为您处理其依赖项。
唯一的硬依赖项是 futures-task
和 futures-util
。其余的都是为每个执行器开启支持的可选依赖项。
功能
此 crate 有很多功能。让我们来看看它们。
通用功能
tracing
:当启用时,所有特性都会为tracing-futures::Instrumented
和tracing-futures::WithDispatch
重新实现。timer
:开启futures-timer包。这允许执行器进行异步休眠。在tokio上,您可以选择启用tokio_timer
以启用tokio原生定时器。async_std,当不在wasm上时,提供定时器而不需要此功能。
执行器特定
async_global
:开启来自async-global-executor的执行器。支持Wasm和!Send
任务。async_global_tokio
:确保在AsyncGlobal
上派生的任务有一个tokio反应器运行。AsyncGlobal
将实现TokioIo
特质。async_std
:开启来自async-std包的执行器。支持Wasm和!Send
任务。async_std_tokio
:确保在AsyncStd
上派生的任务有一个tokio反应器运行。AsyncStd
将实现TokioIo
特质。glommio
:开启来自glommio包的执行器。单线程,仅支持Linux 5.8+。支持!Send
任务。tokio_ct
:Tokio 当前线程,启用tokio包的单线程运行时。支持!Send
任务。tokio_tp
:Tokio 线程池,启用tokio包的线程池运行时。tokio_timer
:将在tokio上启用time
特性,并在创建的任何tokio运行时上调用enable_time()
。对于tokio运行时,此特性优先于timer
特性。tokio_io
:将在tokio上启用net
和process
特性,并在创建的任何tokio运行时上调用enable_reactor()
。TokioCt
和TokioTp
将实现TokioIo
特质。localpool
: 启用来自 futures-executor 的单线程执行器。支持!Send
任务。将重新导出LocalPool
和LocalSpawner
,并实现我们的特质。threadpool
: 启用来自 futures-executor 的线程池执行器。将重新导出ThreadPool
,并实现我们的特质。bindgen
: 启用来自 wasm-bindgen-futures 的单线程执行器。仅适用于 Wasm。支持!Send
任务。
安全性
该包本身使用 #[ forbid(unsafe_code) ]
。
我们的依赖项使用不安全代码。
性能
大多数包装器非常简洁,但 Spawn
和 LocalSpawn
特质确实意味着对未来的装箱。由于执行器将未来装箱以将其放入队列,因此每个 spawn 可能需要 2 个堆分配。
JoinHandle
使用来自 tokio 和 async-std 的本地 JoinHandle
类型来避免 RemoteHandle
的开销,但对于 async-std,将未来包装在 Abortable
中以在所有执行器之间创建一致的行为。提供的 JoinHandle
在丢弃时会取消其未来,除非你调用其 detach
。
SpawnHandle
和 LocalSpawnHandle
需要对未来进行两次装箱,就像 Spawn
和 LocalSpawn
一样。
所有执行器的现有基准测试可以在 executor_benchmarks 中找到。
使用
对于 API 提供者
当编写需要 spawn 的库时,你很可能不希望将客户端锁定在某个框架上。通常,设置自己的线程池来 spawn 未来是不恰当的。决定在哪里 spawn 未来是应用程序开发者的责任,如果库引入了对框架的额外依赖,可能会不受欢迎。
为了解决这个问题,你可以从客户端代码中接受一个执行器作为参数,并在提供的执行器上 spawn 你的未来。目前,只有两个广泛可用的特质是 futures 库中的 Spawn
和 LocalSpawn
。不幸的是,其他执行器提供者没有实现这些特质。因此,通过发布依赖于这些特质的 API,你会限制客户端只能使用 futures 的执行器,或者开始实现自己的包装器,该包装器实现这些特质。
Async_executors 提供了在各个执行器上实现包装器的包装器,例如 tokio、async_std、wasm_bindgen 等。因此,你可以只使用特质界限,并在用户想要使用支持的任何执行器时将其指向此包。
所有包装器也实现了 Clone
、Debug
,以及无尺寸的包装器也实现了 Copy
。你可以在你的 API 中表达你需要克隆:impl Spawn + Clone
。
请注意,你绝对不应该在异步上下文中使用 block_on
。根据执行器,这可能会挂起或崩溃。我们使用的某些后端,如 tokio 和 futures 中的 RemoteHandle
,使用 catch_unwind
,因此请尽量保持未来的 unwind 安全。
使用句柄进行 spawn
您可以使用 SpawnHandle
和 LocalSpawnHandle
特性作为获取连接句柄的边界。
示例
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 );
}
如上述示例所示,future 的输出是 SpawnHandle
的类型参数。这是必要的,因为如果将其放在方法上,则会使特性不再对象安全,这意味着除非作为类型参数,否则无法存储。
定义您需要的组合能力最好的方式是创建自己的特性别名(这里通过来自 trait_set 包的宏展示,但您也可以根据需要编写 blanket 实现)
use async_executors::*;
trait_set::trait_set!
{
pub trait LibExec = SpawnHandle<()> + SpawnHandle<u8> + Timer + YieldNow + Clone;
}
pub fn lib_function( _exec: impl LibExec ) {}
SpawnHandle
的所有实现者都必须支持任何输出类型。因此,向 LibExec
添加更多的 SpawnHandle
边界不应破坏兼容性。
对于 API 消费者
基本上,您可以将 async_executors 中提供的包装类型传递给接受以下任何类型的 API。还实现了 Rc
、Arc
、&
、&mut
、Box
和来自 tracing-futures 的 Instrumented
和 WithDispatch
的特性。
impl Spawn
impl LocalSpawn
impl SpawnHandle<T>
impl LocalSpawnHandle<T>
impl SpawnBlocking
impl YieldNow
impl Timer
impl TokioIo
所有包装器也实现了 Clone
、Debug
,并且零大小类型也实现了 Copy
。
一些执行器有些特别,所以请确保查看您打算使用的 API 文档。一些还提供了额外的功能,如 block_on
方法,这将调用特定框架的 block_on
而不是来自 futures 的 block_on
。
示例
use
{
async_executors :: { AsyncStd, SpawnHandle, TokioTp } ,
std :: { convert::TryFrom } ,
};
fn needs_exec( exec: impl SpawnHandle<()> + SpawnHandle<String> ){};
// AsyncStd is zero sized, so it's easy to instantiate.
//
needs_exec( AsyncStd );
// We need a builder type for tokio, as we guarantee by the type of TokioTp that it
// will be a threadpool.
//
let tp = TokioTp::new().expect( "build threadpool" );
needs_exec( tp );
有关更多示例,请查看 示例目录。如果您想获得一个更完善的 API 来遵守结构化并发,请查看 async_nursery。
API
API 文档可在 docs.rs 上找到。
贡献
请查看 贡献指南。
测试
运行 ci/test.bash
和 ci/wasm.bash
以运行所有测试。
行为准则
公民行为准则第 4 点“不可接受的行为”中描述的任何行为在这里都不受欢迎,并可能导致您被禁止。如果任何包括维护者和项目管理员在内的个人未能遵守这些/您的限制,您有权对他们进行指责。
许可
依赖项
~1–18MB
~204K SLoC