且构网

分享程序员开发的那些事...
且构网 - 分享程序员编程开发的那些事

如何在另一个 Tokio 运行时内创建 Tokio 运行时而不会出现错误“无法从运行时内启动运行时"?

更新时间:2023-12-06 15:40:16

解决问题

这是一个简化的例子:

Solving the problem

This is a reduced example:

use tokio; // 1.0.2

#[tokio::main]
async fn inner_example() {}

#[tokio::main]
async fn main() {
    inner_example();
}

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.', /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.0.2/src/runtime/enter.rs:39:9

为避免这种情况,您需要在完全独立的线程上运行创建第二个 Tokio 运行时的代码.最简单的方法是使用 std::thread::生成:

To avoid this, you need to run the code that creates the second Tokio runtime on a completely independent thread. The easiest way to do this is to use std::thread::spawn:

use std::thread;

#[tokio::main]
async fn inner_example() {}

#[tokio::main]
async fn main() {
    thread::spawn(|| {
        inner_example();
    }).join().expect("Thread panicked")
}

为了提高性能,您可能希望使用线程池而不是每次都创建一个新线程.方便的是,Tokio 本身通过 spawn_blocking:

For improved performance, you may wish to use a threadpool instead of creating a new thread each time. Conveniently, Tokio itself provides such a threadpool via spawn_blocking:

#[tokio::main]
async fn inner_example() {}

#[tokio::main]
async fn main() {
    tokio::task::spawn_blocking(|| {
        inner_example();
    }).await.expect("Task panicked")
}

在某些情况下,您实际上不需要创建第二个 Tokio 运行时,而是可以重用父运行时.为此,您需要传入 Handle 到外部运行时.您可以选择使用轻量级执行器,例如 futures::executor 阻塞结果,如果你需要等待工作完成:

In some cases you don't need to actually create a second Tokio runtime and can instead reuse the parent runtime. To do so, you pass in a Handle to the outer runtime. You can optionally use a lightweight executor like futures::executor to block on the result, if you need to wait for the work to finish:

use tokio::runtime::Handle; // 1.0.2

fn inner_example(handle: Handle) {
    futures::executor::block_on(async {
        handle
            .spawn(async {
                // Do work here
            })
            .await
            .expect("Task spawned in Tokio executor panicked")
    })
}

#[tokio::main]
async fn main() {
    let handle = Handle::current();

    tokio::task::spawn_blocking(|| {
        inner_example(handle);
    })
    .await
    .expect("Blocking task panicked")
}

另见:

更好的方法是首先避免创建嵌套的 Tokio 运行时.理想情况下,如果一个库使用异步执行器,它也会提供直接异步功能,以便您可以使用自己的执行器.

A better path is to avoid creating nested Tokio runtimes in the first place. Ideally, if a library uses an asynchronous executor, it would also offer the direct asynchronous function so you could use your own executor.

值得查看 API 以查看是否有非阻塞替代方案,如果没有,则在项目存储库中提出问题.

It's worth looking at the API to see if there is a non-blocking alternative, and if not, raising an issue on the project's repository.

您还可以重新组织您的代码,以便 Tokio 运行时不是嵌套的而是顺序的:

You may also be able to reorganize your code so that the Tokio runtimes are not nested but are instead sequential:

struct Data;

#[tokio::main]
async fn inner_example() -> Data {
    Data
}

#[tokio::main]
async fn core(_: Data) {}

fn main() {
    let data = inner_example();
    core(data);
}