使用 async 和 await
This feature is still in active development. See the related issue.
#[pyfunction] 與 #[pymethods] 屬性也支援 async fn。
#![allow(dead_code)]
#[cfg(feature = "experimental-async")] {
use std::{thread, time::Duration};
use futures::channel::oneshot;
use pyo3::prelude::*;
#[pyfunction]
#[pyo3(signature=(seconds, result=None))]
async fn sleep(seconds: f64, result: Option<Py<PyAny>>) -> Option<Py<PyAny>> {
let (tx, rx) = oneshot::channel();
thread::spawn(move || {
thread::sleep(Duration::from_secs_f64(seconds));
tx.send(()).unwrap();
});
rx.await.unwrap();
result
}
}
Python awaitables instantiated with this method can only be awaited in asyncio context. Other Python async runtime may be supported in the future.
Send + 'static 限制
由 #[pyfunction] 修飾的 async fn 所產生的 future 必須是 Send + 'static,才能嵌入為 Python 物件。
因此,async fn 的參數與回傳型別也必須是 Send + 'static,所以無法使用像 async fn does_not_compile<'py>(arg: Bound<'py, PyAny>) -> Bound<'py, PyAny> 的簽名。
不過,方法接收者有例外,因此 async 方法可以接受 &self/&mut self。請注意,這表示類別實例會在回傳的 future 完成前一直被借用,甚至跨越 yield 點與等待 I/O 操作完成的期間。因此,在 future 仍被輪詢時,其他方法無法取得獨占借用。這與 Rust 中 async 方法的一般行為相同,但由於普遍的共享可變性,對與 Python 互動的 Rust 程式碼而言問題更大。強烈建議優先使用共享借用 &self 而非獨占借用 &mut self,以避免執行期的競態借用檢查失敗。
隱式附加到直譯器
即使無法將 py: Python<'py> token 傳給 async fn,我們在 future 執行期間仍會附加到直譯器——就像一般不帶 Python<'py>/Bound<'py, PyAny> 參數的 fn 一樣。
仍可透過 Python::attach 取得 Python 標記;由於 attach 具備可重入性且已最佳化,其成本可忽略不計。
跨越 .await 時從直譯器分離
目前沒有簡單的方法在等待 future 時從直譯器分離,但解法正在開發中。
目前建議的替代作法如下:
use std::{
future::Future,
pin::{Pin, pin},
task::{Context, Poll},
};
use pyo3::prelude::*;
struct AllowThreads<F>(F);
impl<F> Future for AllowThreads<F>
where
F: Future + Unpin + Send,
F::Output: Send,
{
type Output = F::Output;
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let waker = cx.waker();
Python::attach(|py| {
py.detach(|| pin!(&mut self.0).poll(&mut Context::from_waker(waker)))
})
}
}
取消
可透過 CancelHandle 型別捕捉 Python 端的取消事件,方法是在函式參數上加註 #[pyo3(cancel_handle)]。
#![allow(dead_code)]
#[cfg(feature = "experimental-async")] {
use futures::FutureExt;
use pyo3::prelude::*;
use pyo3::coroutine::CancelHandle;
#[pyfunction]
async fn cancellable(#[pyo3(cancel_handle)] mut cancel: CancelHandle) {
futures::select! {
/* _ = ... => println!("done"), */
_ = cancel.cancelled().fuse() => println!("cancelled"),
}
}
}
Coroutine 型別
為了讓 Rust future 能在 Python 中被 await,PyO3 定義了 Coroutine 型別,並實作了 Python 的協程協定。
每次 coroutine.send 呼叫都會被轉換為 Future::poll 呼叫。若宣告了 CancelHandle 參數,傳給 coroutine.throw 的例外會儲存在其中,並可透過 CancelHandle::cancelled 取得;否則會取消 Rust future,並重新拋出該例外;
The type does not yet have a public constructor until the design is finalized.