You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PyO3:在Rust中异步填充Python二维数组的报错问题

问题:从Rust异步填充Python数组的编译错误解决

问题描述

能否从Rust中异步填充(或创建)Python数组?我尝试了以下代码,但编译器报错“borrowed data escapes outside of function”:

// 这段代码无法运行。传入numpy数组,在异步函数里将其填充为0.0,行数乘以5,休眠1秒
#[pyfunction]
fn xsleep2<'a>(py: Python<'a>, val: &'a PyArray2<f32>) -> PyResult<&'a PyAny> {
    let mut val = unsafe { val.as_array_mut() };

    pyo3_asyncio::tokio::future_into_py(py, async move {
        val.fill(0.0);
        let times5 = val.dim().0 * 5;

        sleep(Duration::from_secs(1)).await;
        let ret = Python::with_gil(|py| {
            let ret = times5;
            ret.into_py(py)
        });
        Ok(ret)
    })
}

cargo check完整错误信息:

error[E0521]: borrowed data escapes outside of function
  --> src\lib.rs:46:5
   |
43 |   fn xsleep2<'a>(py: Python<'a>, val: &'a PyArray2<f32>) -> PyResult<&'a PyAny> {
   |              --  -- `py`是仅在函数体内有效的引用
   |              |
   |              生命周期`'a`在这里定义
...
46 | /     pyo3_asyncio::tokio::future_into_py(py, async move {
47 | |         val.fill(0.0);
48 | |         let times5 = val.dim().0 * 5;
49 | |
...  |
55 | |         Ok(ret)
56 | |     })
   | |      ^
   | |      |
   | |______`py`在这里逃出了函数体
   |        参数要求`'a`必须存活得比`'static`久
   |
   = note: 这个要求源于类型`ArrayBase<ViewRepr<&mut f32>, Dim<[usize; 2]>>`,它使得泛型参数`ViewRepr<&mut f32>`是不变的
   = note: 结构体`ArrayBase<S, D>`在参数`S`上是不变的

错误原因

核心矛盾是异步闭包捕获了带有函数内生命周期'a的引用,但future_into_py要求传入的Future必须满足'static生命周期:

  1. val.as_array_mut()生成的可变视图依赖原PyArray2的'a生命周期,被移动到异步闭包后,无法保证原生命周期覆盖异步任务的整个执行周期(异步任务可能在原函数返回后很久才运行)。
  2. Tokio Runtime调度异步任务时,要求任务内的所有捕获变量不能依赖临时生命周期,否则会导致悬垂引用。

解决方案:异步填充现有Python数组

通过持有所有权的PyObject传递数组,在异步任务内部重新获取GIL后操作数组,避免生命周期逃逸:

use pyo3::prelude::*;
use pyo3::types::PyArray2;
use pyo3_asyncio::tokio;
use tokio::time::{sleep, Duration};

#[pyfunction]
fn xsleep2(py: Python<'_>, val: &PyArray2<f32>) -> PyResult<PyObject> {
    // 将PyArray2转为拥有所有权的PyObject,满足'static生命周期要求
    let val = val.into_py(py);

    tokio::future_into_py(py, async move {
        // 模拟异步操作(如IO、计算)
        sleep(Duration::from_secs(1)).await;

        // 重新获取GIL,在GIL保护下操作Python对象
        Python::with_gil(|py| {
            // 将PyObject转回PyArray2
            let val = val.as_ref(py).downcast::<PyArray2<f32>>()?;
            // 安全获取可变数组视图(无需unsafe,GIL已保证线程安全)
            let mut arr = val.as_array_mut();
            
            // 执行填充操作
            arr.fill(0.0);
            let times5 = arr.dim().0 * 5;

            // 返回结果
            Ok(times5.into_py(py))
        })
    })
}

关键修改点:

  • 函数返回类型改为PyResult<PyObject>:异步任务返回拥有所有权的对象,而非临时引用。
  • 使用into_py(py)将&PyArray2转为PyObject:拥有所有权的对象可安全移动到异步闭包,满足'static要求。
  • 异步任务内通过Python::with_gil重新获取GIL:所有Python对象操作必须在GIL保护下执行,避免线程安全问题。

扩展:异步创建新Python数组

如果需求是创建新数组而非修改现有数组,可在异步任务中完成计算后,在GIL下生成数组返回:

#[pyfunction]
fn create_async_array(py: Python<'_>, rows: usize, cols: usize) -> PyResult<PyObject> {
    tokio::future_into_py(py, async move {
        // 模拟异步数据计算
        sleep(Duration::from_secs(1)).await;
        let data = vec![0.0; rows * cols];

        Python::with_gil(|py| {
            // 创建新的PyArray2并返回
            let arr = PyArray2::from_vec(py, data, (rows, cols))?;
            Ok(arr.into_py(py))
        })
    })
}

注意事项

  • 异步场景下,禁止将带有函数内生命周期的引用传入闭包,必须使用PyObject或Py<T>等拥有所有权的类型传递Python对象。
  • 所有操作Python对象的代码必须包裹在Python::with_gil中,确保GIL保护下的线程安全。
  • 避免直接使用unsafe { val.as_array_mut() }:除非能保证视图生命周期内GIL持续持有,异步场景下这种操作极易引发安全问题。

内容的提问来源于stack exchange,提问作者Carl

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 14:13:10