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。
另外还有两个次要问题:
- 两次插入
CONTENT_TYPE头,后一次会直接覆盖前一次,正确做法是分别设置Accept和Content-Type两个独立的请求头。 - 在异步
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
相关产品推荐
相关产品推荐

