如何在Tokio运行时的同步环境中执行异步函数?SeaORM数据库连接同步初始化报错求助
嗨,我完全懂你想同步初始化SeaORM数据库连接的需求——毕竟同步调用在很多场景下确实更省心,不管是业务代码里还是测试环节,而且你也说了只初始化一次,完全接受阻塞。不过你碰到的这个报错:
Cannot start a runtime from within a runtime. This happens because a function (like
block_on) attempted to start a new Tokio runtime while already running inside a Tokio runtime.
其实是Tokio的典型限制:你不能在一个已经存在的Tokio运行时里再启动另一个运行时。比如你用#[tokio::test]的时候,测试框架已经帮你启动了一个运行时,这时候如果再用Runtime::new().unwrap().block_on(...)就会触发这个错误。下面分两种场景给你解决办法:
一、普通应用的同步初始化(非测试环境)
如果是在应用的main函数里做初始化,你可以在最外层创建一个Tokio运行时,然后用它来block_on异步的连接初始化逻辑,之后把连接保存下来,后续需要执行异步操作时,再用这个运行时来调度:
use sea_orm::{Database, DatabaseConnection}; use tokio::runtime::Runtime; fn main() { // 先创建一个Tokio运行时(整个应用只创建一次) let rt = Runtime::new().expect("Failed to create Tokio runtime"); // 同步初始化数据库连接 let db_conn: DatabaseConnection = rt.block_on(async { Database::connect("your-database-url-here") .await .expect("Failed to connect to database") }); // 之后在同步代码里如果需要调用SeaORM的异步方法,用rt.block_on包裹就行 rt.block_on(async { // 举个例子:查询数据 // let users = User::find().all(&db_conn).await.unwrap(); }); }
二、测试环境下的处理(使用#[tokio::test])
测试场景下,#[tokio::test]已经自动帮你启动了Tokio运行时,所以完全没必要再手动创建新的运行时,直接写异步测试代码就好:
use sea_orm::{Database, DatabaseConnection}; #[tokio::test] async fn test_database_ops() { // 直接在异步测试上下文里初始化连接 let db_conn = Database::connect("your-test-db-url") .await .expect("Test database connection failed"); // 测试逻辑直接用异步调用,完全贴合SeaORM的设计 let user_count = User::find().count(&db_conn).await.unwrap(); assert_eq!(user_count, 0, "Test database should be empty"); }
如果你的测试里确实有一部分同步代码需要调用SeaORM的异步方法,可以获取当前运行时的Handle,用它来调度异步操作,而不是新建运行时:
use tokio::runtime::Handle; use sea_orm::{Database, DatabaseConnection}; #[tokio::test] async fn test_sync_code_with_async_db() { let db_conn = Database::connect("your-test-db-url") .await .expect("Test database connection failed"); // 获取当前Tokio运行时的Handle let rt_handle = Handle::current(); // 在同步代码块(比如线程里)调用异步方法 std::thread::spawn(move || { rt_handle.block_on(async { let user_count = User::find().count(&db_conn).await.unwrap(); assert_eq!(user_count, 0); }); }).join().unwrap(); }
总的来说,核心原则就是不要嵌套创建Tokio运行时:要么在外部(比如main函数)创建唯一的运行时来处理所有异步逻辑,要么在已有的运行时上下文里直接用异步调用,或者通过Handle来调度。
备注:内容来源于stack exchange,提问作者ZiiMakc

