簡體   English   中英

有沒有辦法從 python 調用 rust 的異步接口?

[英]Is there a way to call async interface of rust from python?

我將reqwest的 reqwest 的一些函數包裝到req.lib文件中,並使用cffi從 python 成功調用它。 然而reqwest::blocking::Client迫使我在 python 中使用多線程。 我發現reqwest可以在 rust 中以異步模式調用。 我想知道有沒有辦法使req.lib異步? 即使是半異步對我來說也可以。

例如,當前存根簽名是:

#[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 這些是不同且不兼容的庫。 在那里,您不能簡單地在語言之間共享未來,它必須以創建它的相同語言運行。

至於您的示例,這意味着您需要在 rust 執行器中運行一個Client (例如在 tokio 中),然后從中獲得反饋。 作為最簡單的方法,您可以創建一個全局方法:

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

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

然后在生成之后你需要有一個反饋,所以你可以使用一些帶有狀態和結果的地圖:

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 _
}

游樂場鏈接。

請記住,上述解決方案有其優點:

  • 結果被緩存,可以多次獲取;
  • API(希望)是線程安全的;
  • 讀寫鎖分離,這可能是比互斥鎖更快的解決方案;

以及顯着的缺點:

  • RESULTS無限增長,永遠不會清除;
  • 線程安全使事情變得有點復雜,所以可能不需要和thread_local! 可用於全局變量而不是鎖;
  • 缺乏適當的錯誤處理;
  • 使用了 RwLock,它有時可能比其他一些原語表現得更差;
  • is_finished處的STATUSES獲得寫訪問權限,但最好先獲得讀訪問權限;

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM