能否在Rust Diesel事务块中运行异步函数?
问题描述
在使用Rust Diesel处理事务时,尝试在事务块中运行异步任务,编写了如下最小复现代码:
use diesel::result::Error; use diesel::Connection; use rust_wheel::config::db::config; use tokio::runtime::Runtime; #[tokio::main] async fn main() { let mut connection = config::connection("TEX_DATABASE_URL".to_string()); let _trans_result: Result<(), Error> = connection.transaction(|_connection| { let rt = Runtime::new().unwrap(); Ok(rt.block_on(async { do_create_proj_trans().await })) }); } async fn do_create_proj_trans() { println!("doing...") }
运行后触发错误:
thread 'main' panicked at 'Cannot start a runtime from within a runtime. This happens because a function (like `block_on`) attempted to block the current thread while the thread is being used to drive asynchronous tasks.', /Users/xiaoqiangjiang/.cargo/registry/src/mirrors.tuna.tsinghua.edu.cn-df7c3c540f42cdbd/tokio-1.32.0/src/runtime/scheduler/multi_thread/mod.rs:86:9 note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
移除手动创建的Runtime后,又出现新错误:
`await` is only allowed inside `async` functions and blocks only allowed inside `async` functions and blocks
项目的Cargo.toml配置:
[package] name = "rust-learn" version = "0.1.0" edition = "2018" [dependencies] tokio = { version = "1.17.0", features = ["full"] } serde = { version = "1.0.64", features = ["derive"] } serde_json = "1.0.64" futures = "0.3" tokio-stream = "0.1" rust_wheel = { git = "https://github.com/jiangxiaoqiang/rust_wheel.git", branch = "diesel2.0" } log4rs = "1.2.0" log = "0.4.0" diesel = { version = "2.1.0", features = ["postgres","64-column-tables","chrono","serde_json"] }
核心疑问:能否在Rust Diesel事务块中运行异步函数?
解决方案
问题根源
- Diesel的
transaction方法是同步API,它接收的闭包是同步闭包,不支持直接在内部使用await。 - 手动创建新Tokio Runtime报错,是因为当前代码已经在
#[tokio::main]启动的Runtime中运行,Tokio禁止在Runtime内部嵌套创建新Runtime以避免线程阻塞和调度冲突。
可行方案
方案1:分离同步事务与异步逻辑(推荐)
将同步的事务操作与异步任务解耦,用tokio::task::spawn_blocking把同步事务逻辑放到Tokio的阻塞线程池中执行,避免阻塞异步线程。事务完成后再执行异步任务:
use diesel::result::Error; use diesel::Connection; use rust_wheel::config::db::config; use tokio::task; #[tokio::main] async fn main() { // 把同步事务逻辑放到阻塞线程池执行 let trans_result: Result<(), Error> = task::spawn_blocking(|| { let mut connection = config::connection("TEX_DATABASE_URL".to_string()); connection.transaction(|_connection| { // 这里执行同步的事务操作 println!("执行同步事务逻辑"); Ok(()) }) }).await.unwrap(); // 事务完成后运行异步任务 do_create_proj_trans().await; } async fn do_create_proj_trans() { println!("doing...") }
方案2:在事务中复用当前Runtime(不推荐,仅适用于短耗时异步操作)
如果异步任务必须依赖事务上下文(比如需要使用事务中的数据库连接),可以复用当前Tokio Runtime,而非新建:
use diesel::result::Error; use diesel::Connection; use rust_wheel::config::db::config; use tokio::runtime::Handle; #[tokio::main] async fn main() { let handle = Handle::current(); let mut connection = config::connection("TEX_DATABASE_URL".to_string()); let _trans_result: Result<(), Error> = connection.transaction(|_connection| { // 复用当前Runtime执行异步任务 handle.block_on(async { do_create_proj_trans().await }); Ok(()) }); } async fn do_create_proj_trans() { println!("doing...") }
注意事项
- Diesel的事务API设计为同步,尽量避免在事务内部执行异步操作,否则可能阻塞Tokio的异步线程池,影响整体性能。
- 如果异步操作耗时较长,必须使用方案1的分离模式,确保同步阻塞逻辑不会占用异步线程。
- 若异步任务需要访问事务中的数据库连接,需注意连接的线程安全性:Diesel的连接默认不是
Send的,不能直接跨线程传递,这种场景下方案2是唯一选择,但要严格控制异步任务的执行时长。
内容的提问来源于stack exchange,提问作者Dolphin
相关产品推荐
相关产品推荐

