5 个不稳定版本
0.3.2 | 2023年12月9日 |
---|---|
0.3.1 | 2023年10月11日 |
0.3.0 | 2023年10月10日 |
0.2.0 | 2023年10月8日 |
0.1.0 | 2023年10月2日 |
#20 在 #cpython
每月113次下载
36KB
723 行
pyo3-async
PyO3 对各种 Python 异步框架的绑定。
此包已废弃
此包是关于在 PyO3 中实现异步支持的一个实验。
实验成功,因为异步支持最终正在实现中,参见 相关。
因此,此包现在已废弃。如果您想在官方发布前尝试异步支持,应使用 最后 PR 的分支,或 master
(实现仍不完整,但通常稳定)。
文档
工作原理
Rust 和 Python 中的异步实现没有太大区别。Rust 使用回调(通过 std::task::Waker
)唤醒相关执行器,而 Python 的 Asyncio.Future
也注册了一个回调来唤醒事件循环。
为什么不使用 Rust 回调唤醒 Python 事件循环,反之亦然?这就是全部。
与 PyO3 Asyncio 的区别
- PyO3 Asyncio 需要Python 和 Rust 两边都运行异步运行时,而此包不需要;
- PyO3 Asyncio 只关注 asyncio,而此包显然支持 asyncio,但也支持 trio 或 anyio;
- 此包提供了对 GIL 释放的控制;
- 此crate提供
#[pyfunction]
/#[pymethods]
宏。
示例
您可以使用Maturin构建此模块
#[pymodule]
fn example(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(async_sleep_asyncio, m)?)?;
m.add_function(wrap_pyfunction!(async_sleep_trio, m)?)?;
m.add_function(wrap_pyfunction!(sleep_sniffio, m)?)?;
m.add_function(wrap_pyfunction!(spawn_future, m)?)?;
m.add_function(wrap_pyfunction!(count_asyncio, m)?)?;
m.add_function(wrap_pyfunction!(count_trio, m)?)?;
Ok(())
}
fn tokio() -> &'static tokio::runtime::Runtime {
use std::sync::OnceLock;
static RT: OnceLock<tokio::runtime::Runtime> = OnceLock::new();
RT.get_or_init(|| tokio::runtime::Runtime::new().unwrap())
}
async fn sleep(seconds: u64) {
let sleep = async move { tokio::time::sleep(std::time::Duration::from_secs(seconds)).await };
tokio().spawn(sleep).await.unwrap();
}
fn count(until: i32, tick: u64) -> impl Stream<Item = PyResult<i32>> + Send {
futures::stream::unfold(0, move |i| async move {
if i == until {
return None;
}
sleep(tick).await;
Some((PyResult::Ok(i), i + 1))
})
}
// Works with async function
// It generates an `async_sleep_asyncio` function to be exported (see #[pymodule] above)
#[pyfunction]
async fn sleep_asyncio(seconds: u64) {
sleep(seconds).await;
}
// Specify Python async backend and GIL release
#[pyfunction(trio, allow_threads)]
async fn sleep_trio(seconds: u64) {
sleep(seconds).await;
}
// Coroutine can be manually instantiated
#[pyfunction]
fn sleep_sniffio(seconds: u64) -> pyo3_async::sniffio::Coroutine {
pyo3_async::sniffio::Coroutine::from_future(async move {
sleep(seconds).await;
PyResult::Ok(())
})
}
#[pyfunction]
fn spawn_future(fut: PyObject) {
tokio().spawn(async move {
pyo3_async::asyncio::FutureWrapper::new(fut, None)
.await
.unwrap();
println!("task done")
});
}
#[pyfunction]
fn count_asyncio(until: i32, tick: u64) -> pyo3_async::asyncio::AsyncGenerator {
pyo3_async::asyncio::AsyncGenerator::from_stream(count(until, tick))
}
#[pyfunction]
fn count_trio(until: i32, tick: u64) -> pyo3_async::trio::AsyncGenerator {
pyo3_async::trio::AsyncGenerator::from_stream(count(until, tick))
}
并执行以下Python代码
import asyncio
import trio
import example # built with maturin
async def asyncio_main():
await example.sleep_asyncio(1)
# sleep 1s
await example.sleep_sniffio(1)
# sleep 1s
example.spawn_future(asyncio.create_task(asyncio.sleep(1)))
await asyncio.sleep(2)
# print "done" after 1s
async for i in example.count_asyncio(2, 1):
print(i)
# sleep 1s, print 0
# sleep 1s, print 1
async def trio_run():
await example.sleep_trio(1)
# sleep 1s
await example.sleep_sniffio(1)
# sleep 1s
async for i in example.count_trio(2, 1):
print(i)
# sleep 1s, print 0
# sleep 1s, print 1
asyncio.run(asyncio_main())
print("======================")
trio.run(trio_run)
依赖关系
~4–9.5MB
~87K SLoC