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

Rust同步上下文调用异步HTTP RPC代码阻塞问题求助

问题描述

我需要在Rust的同步上下文中调用HTTP RPC接口,但无法修改当前上下文的调用方式为异步。尝试用executor::block_on(get_snowflake_id())调用异步函数get_snowflake_id时,代码陷入永久阻塞。之后又尝试了以下实现:

pub fn get_uniq_id() -> Option<i64> {
    task::block_in_place(|| {
        tokio::runtime::Handle::current().block_on(get_snowflake_id())
    })
}

但出现错误提示can call blocking only when running on the multi-threaded runtime。我的应用入口为:

#[actix_web::main]
async fn main() -> std::io::Result<()> {}

get_snowflake_id函数的具体实现如下:

use std::env;
use log::error;
use reqwest::Client;
use rust_wheel::model::response::api_response::ApiResponse;

pub async fn get_snowflake_id() -> Option<i64> {
    let client = Client::new();
    let infra_url = env::var("INFRA_URL").expect("INFRA_URL must be set");
    let url = format!("{}{}", infra_url, "/infra-inner/util/uniqid/gen");
    let resp = client
        .get(format!("{}", url))
        .body("{}")
        .send()
        .await;
    if let Err(e) = resp {
        error!("get id failed: {}", e);
        return None;
    }
    let text_response = resp.unwrap().text().await;
    if let Err(e) = text_response {
        error!("extract text failed: {}", e);
        return None;
    }
    let resp_str = text_response.unwrap_or_default();
    let resp_result = serde_json::from_str::<ApiResponse<i64>>(&resp_str);
    if let Err(pe) = resp_result {
        error!("parse failed: {}, response: {}", pe, &resp_str);
        return None;
    }
    Some(resp_result.unwrap().result)
}

请问该如何正确在同步上下文中调用这段异步代码?

解决方案

核心原因

问题本质是Actix Web默认使用单线程Runtime,而block_in_place和Handle::current().block_on依赖多线程Runtime才能安全执行阻塞操作;同时直接在异步上下文嵌套block_on会导致线程死锁——Runtime的工作线程被阻塞后,无法调度后续异步任务完成。

可行实现方案

方案1:创建独立多线程Runtime

在同步函数内部创建独立的Tokio多线程Runtime,完全隔离Actix的Runtime,避免冲突:

pub fn get_uniq_id() -> Option<i64> {
    // 初始化多线程Runtime
    let rt = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");
    rt.block_on(get_snowflake_id())
}

优点:无需修改现有Actix配置,逻辑独立;缺点:每次调用都会创建Runtime,可通过全局缓存优化。

方案2:全局缓存Runtime实例

用全局懒加载的方式缓存Runtime,避免重复初始化的性能损耗:

use once_cell::sync::Lazy;

// 全局缓存多线程Runtime
static RUNTIME: Lazy<tokio::runtime::Runtime> = Lazy::new(|| {
    tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime")
});

pub fn get_uniq_id() -> Option<i64> {
    RUNTIME.block_on(get_snowflake_id())
}

需要在Cargo.toml中添加依赖:

once_cell = "1.18.0"

方案3:切换Actix到多线程Runtime

修改应用入口,强制使用多线程Runtime,这样原有的block_in_place写法即可正常工作:

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    // 构建多线程Runtime并替换默认配置
    let rt = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(num_cpus::get())
        .enable_all()
        .build()
        .unwrap();
    
    rt.block_on(async {
        // 原有main逻辑移至此处
        Ok(())
    })
}

同步函数修改为:

pub fn get_uniq_id() -> Option<i64> {
    tokio::task::block_in_place(|| {
        tokio::runtime::Handle::current().block_on(get_snowflake_id())
    })
}

注意:此方案需确保整个应用都基于多线程Runtime运行,避免单线程场景下的死锁风险。

额外优化建议

reqwest::Client是线程安全且可重用的,可提前全局初始化,避免每次调用get_snowflake_id都创建新实例:

use once_cell::sync::Lazy;

static HTTP_CLIENT: Lazy<reqwest::Client> = Lazy::new(reqwest::Client::new);

pub async fn get_snowflake_id() -> Option<i64> {
    let client = &HTTP_CLIENT;
    // 后续业务逻辑保持不变
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 17:33:18