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

能否在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事务块中运行异步函数?


解决方案

问题根源

  1. Diesel的transaction方法是同步API,它接收的闭包是同步闭包,不支持直接在内部使用await。
  2. 手动创建新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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 21:30:20