gpt4 book ai didi

python - 有没有办法从Python调用Rust的异步接口(interface)?

转载 作者:行者123 更新时间:2023-12-03 11:45:17 26 4
gpt4 key购买 nike

我将 rust 的 reqwest 的一些函数包装到 req.lib 文件中,并使用 cffi 从 python 成功调用它。然而,reqwest::blocking::Client 迫使我在 python 中使用多线程。我发现 reqwest 可以在 Rust 中以异步模式调用。我想知道有没有办法使 req.lib 异步?即使是半异步对我来说也可以。

例如,当前 stub 签名是:

#[no_mangle]
pub extern "C" fn urlopen(url: *const c_char) -> *mut c_char

我可以写这样的东西吗:

#[no_mangle]
pub extern "C" fn urlopen(url: *const c_char) -> u64 // return request unique id

#[no_mangle]
pub extern "C" fn is_finished(req_id: u64) -> bool // whether given request is done

#[no_mangle]
pub extern "C" fn fetch_result(req_id: u64) -> *mut c_char // fetch response

因此 cffi 调用不再锁定主线程。我可以使用单线程来调用多个请求。欢迎任何建议或最佳实践。

最佳答案

异步代码是通过特殊的运行时执行的,对于 python 和 rust 来说,这些是不同且不兼容的库。在那里,你不能简单地在语言之间共享 future,它必须以创建它的相同语言运行。

对于您的示例,这意味着您需要在 rust 执行器(例如在 tokio 中)中运行 Client,然后从中获得反馈。作为最简单的方法,您可以创建一个全局方法:

use lazy_static::lazy_static;
use tokio::runtime::Runtime;

lazy_static! {
static ref RUNTIME: Runtime = Runtime::new().unwrap();
}

然后在生成后您需要获得反馈,因此您可以使用一些带有状态和结果的 map :

use std::collections::HashMap;
use std::sync::RwLock;

use futures::prelude::*;
use tokio::sync::oneshot;

type FutureId = u64;
type UrlResult = reqwest::Result<String>;

type SyncMap<K, V> = RwLock<HashMap<K, V>>;

lazy_static! {
// Map for feedback channels. Once result is computed, it is stored at `RESULTS`
static ref STATUSES: SyncMap<FutureId, oneshot::Receiver<UrlResult>> = SyncMap::default();
// Cache storage for results
static ref RESULTS: SyncMap<FutureId, UrlResult> = SyncMap::default();
}

fn gen_unique_id() -> u64 { .. }

#[no_mangle]
pub extern "C" fn urlopen(url: *const c_char) -> FutureId {
let url: &str = /* convert url */;

let (tx, rx) = oneshot::channel();

RUNTIME.spawn(async move {
let body = reqwest::get(url).and_then(|b| b.text()).await;
tx.send(body).unwrap(); // <- this one should be handled somehow
});

let id = gen_unique_id();

STATUSES.write().unwrap().insert(id, rx);

id
}

这里,对于每个 urlopen 请求 oneshot::channel正在创建,导致执行结果延迟。因此可以检查它是否完成:

#[no_mangle]
pub extern "C" fn is_finished(req_id: u64) -> bool {
// first check in cache
if RESULTS.read().unwrap().contains_key(&req_id) {
true
} else {
let mut res = RESULTS.write().unwrap();
let mut statuses = STATUSES.write().unwrap();

// if nothing in cache, check the feedback channel
if let Some(rx) = statuses.get_mut(&req_id) {
let val = match rx.try_recv() {
Ok(val) => val,
Err(_) => {
// handle error somehow here
return true;
}
};

// and cache the result, if available
res.insert(req_id, val);
true
} else {
// Unknown request id
true
}
}
}

那么获取结果就相当简单了:

#[no_mangle]
pub extern "C" fn fetch_result(req_id: u64) -> *const c_char {
let res = RESULTS.read().unwrap();

res.get(&req_id)
// there `ok()` should probably be handled in some better way
.and_then(|val| val.as_ref().ok())
.map(|val| val.as_ptr())
.unwrap_or(std::ptr::null()) as *const _
}

Playground链接。

请记住,上述解决方案有其优点:

  • 结果被缓存并且可以多次获取;
  • API(希望)是线程安全的;
  • 读写锁是分开的,这可能是比互斥锁更快的解决方案;

还有显着的缺点:

  • RESULTS 无限期增长且永不清除;
  • 线程安全使事情变得有点复杂,因此可能不需要并且thread_local!可以用于全局变量而不是锁;
  • 缺乏适当的错误处理;
  • 使用了 RwLock,它有时可能比其他一些原语表现得更差;
  • STATUSESis_finished 处获取写入访问权限,但最好先获得读取访问权限;

关于python - 有没有办法从Python调用Rust的异步接口(interface)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/62481268/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com