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

Rust使用reqwest初始化客户端类时程序崩溃问题求助

问题:Rust API客户端运行崩溃,报错"Cannot drop a runtime in a context where blocking is not allowed"

代码示例

use reqwest::Error;
use reqwest::header::{HeaderMap, HeaderValue, CONTENT_TYPE};
use reqwest::blocking::Client;
use reqwest::header;
use std::env;

struct GateClient {
    host: &'static str,
    prefix: &'static str,
    url: &'static str,
    key: String,
    secret: String,
    client: Client,
}

impl GateClient {
    fn new(key: &str, secret: &str) -> Self {
        let mut headers = header::HeaderMap::new();
        headers.insert(CONTENT_TYPE, HeaderValue::from_static("Accept=application/json"));
        headers.insert(CONTENT_TYPE, HeaderValue::from_static("Content-Type=application/json"));
    
        let client = Client::builder().default_headers(headers).build().unwrap();
        
        GateClient {
            host: "https://api.gateio.ws",
            prefix: "/api/v4",
            url: "/spot/currency_pairs/",
            key: key.to_string(),
            secret: secret.to_string(),
            client,
        }
    }

    fn get(&self, arg: &str) -> Result<String, reqwest::Error> {
        let url = format!("{}{}{}{}", self.host, self.prefix, self.url, arg);
        println!("{}", url);
        let response = self.client.get(&url).send()?;
        response.text()
    }
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    env::set_var("RUST_BACKTRACE", "Full");

    let client = GateClient::new("a", "b");
    let response = client.get("ETH_USDT").unwrap();
    println!("{}", response);

    Ok(())
}

报错信息

thread 'main' panicked at
/home/nicolas/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.36.0/src/runtime/blocking/shutdown.rs:51:21: Cannot drop a runtime in a context where blocking is not allowed. This
happens when a runtime is dropped from within an asynchronous context.
stack backtrace: 0: rust_begin_unwind


错误原因

核心问题是混用了reqwest的阻塞客户端和Tokio异步运行时:

  • 代码中使用reqwest::blocking::Client(阻塞式HTTP客户端),它内部会创建独立的阻塞线程池处理请求。
  • 但main函数标记了#[tokio::main],运行在Tokio的异步上下文里。程序结束时,GateClient中的阻塞客户端被销毁,这个销毁操作在Tokio异步线程中执行,而阻塞客户端的内部资源(如私有线程池)无法在异步上下文安全销毁,从而触发panic。

另外还有两个次要问题:

  1. 两次插入CONTENT_TYPE头,后一次会直接覆盖前一次,正确做法是分别设置Accept和Content-Type两个独立的请求头。
  2. 在异步main中直接调用阻塞的get方法,会阻塞Tokio的工作线程,破坏异步运行时的性能。

修复方案

方案1:改用reqwest异步客户端(推荐)

将阻塞客户端替换为reqwest的异步版本,完全适配Tokio异步环境:

use reqwest::Error;
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, CONTENT_TYPE};
use reqwest::Client; // 移除blocking模块引用
use std::env;

struct GateClient {
    host: &'static str,
    prefix: &'static str,
    url: &'static str,
    key: String,
    secret: String,
    client: Client,
}

impl GateClient {
    fn new(key: &str, secret: &str) -> Self {
        let mut headers = HeaderMap::new();
        headers.insert(ACCEPT, HeaderValue::from_static("application/json")); // 正确设置Accept头
        headers.insert(CONTENT_TYPE, HeaderValue::from_static("application/json"));
    
        let client = Client::builder().default_headers(headers).build().unwrap();
        
        GateClient {
            host: "https://api.gateio.ws",
            prefix: "/api/v4",
            url: "/spot/currency_pairs/",
            key: key.to_string(),
            secret: secret.to_string(),
            client,
        }
    }

    // 将get方法改为异步函数
    async fn get(&self, arg: &str) -> Result<String, reqwest::Error> {
        let url = format!("{}{}{}{}", self.host, self.prefix, self.url, arg);
        println!("{}", url);
        let response = self.client.get(&url).send().await?; // 添加await关键字
        response.text().await
    }
}

#[tokio::main]
async fn main() -> Result<(), Error> {
    env::set_var("RUST_BACKTRACE", "Full");

    let client = GateClient::new("a", "b");
    let response = client.get("ETH_USDT").await.unwrap(); // 添加await关键字
    println!("{}", response);

    Ok(())
}

方案2:在Tokio阻塞线程中运行阻塞代码

如果坚持使用阻塞客户端,需将阻塞调用放到Tokio的阻塞线程池中执行,避免阻塞异步工作线程,同时确保客户端销毁在安全上下文:

// 其他代码保持不变,仅修改main函数
#[tokio::main]
async fn main() -> Result<(), Error> {
    env::set_var("RUST_BACKTRACE", "Full");

    // 在Tokio阻塞线程池内创建客户端并执行请求
    let response = tokio::task::spawn_blocking(|| {
        let client = GateClient::new("a", "b");
        client.get("ETH_USDT")
    }).await??; // 处理spawn_blocking和get方法的两层Result
    
    println!("{}", response);

    Ok(())
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 09:46:16