Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

使用 asyncawait

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.