【问题标题】:How can I create a Tokio runtime inside another Tokio runtime without getting the error "Cannot start a runtime from within a runtime"?如何在另一个 Tokio 运行时中创建 Tokio 运行时而不会出现错误“无法从运行时中启动运行时”?
【发布时间】:2020-06-23 14:02:06
【问题描述】:

我使用rust_bert 来总结文本。我需要用rust_bert::pipelines::summarization::SummarizationModel::new 设置一个模型,它从互联网上获取模型。它使用tokio 异步执行此操作,(我认为)我遇到的问题是我在另一个 Tokio 运行时中运行 Tokio 运行时,如错误消息所示:

Downloading https://cdn.huggingface.co/facebook/bart-large-cnn/config.json to "/home/(censored)/.cache/.rustbert/bart-cnn/config.json"
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.', /home/(censored)/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.2.21/src/runtime/enter.rs:38:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

我尝试过运行模型同步获取 tokio::task::spawn_blockingtokio::task::block_in_place 但他们都不为我工作。 block_in_place 给出了与不存在相同的错误,而spawn_blocking 真的 似乎对我没有用处。 我也尝试过使summarize_text 异步,但这并没有太大帮助。 Github问题 tokio-rs/tokio#2194 和 Reddit 帖子 "'Cannot start a runtime from within a runtime.' with Actix-Web And Postgresql" 看起来很相似(相同的错误消息),但它们对找到解决方案没有多大帮助。

我遇到问题的代码如下:

use egg_mode::tweet;
use rust_bert::pipelines::summarization::SummarizationModel;

fn summarize_text(model: SummarizationModel, text: &str) -> String {
    let output = model.summarize(&[text]);
    // @TODO: output summarization
    match output.is_empty() {
        false => "FALSE".to_string(),
        true => "TRUE".to_string(),
    }
}

#[tokio::main]
async fn main() {
    let model = SummarizationModel::new(Default::default()).unwrap();

    let token = egg_mode::auth::Token::Bearer("obviously not my token".to_string());
    let tweet_id = 1221552460768202756; // example tweet

    println!("Loading tweet [{id}]", id = tweet_id);
    let status = tweet::show(tweet_id, &token).await;
    match status {
        Err(err) => println!("Failed to fetch tweet: {}", err),
        Ok(tweet) => {
            println!(
                "Original tweet:\n{orig}\n\nSummarized tweet:\n{sum}",
                orig = tweet.text,
                sum = summarize_text(model, &tweet.text)
            );
        }
    }
}

【问题讨论】:

  • 很难回答您的问题,因为它不包含minimal reproducible example。我们无法分辨代码中存在哪些 crate(及其版本)、类型、特征、字段等。如果您尝试在一个全新的 Cargo 项目中重现您的错误,我们会更轻松地为您提供帮助,然后 edit 您的问题将包含完整的示例。您可以使用Rust-specific MRE tips 来减少您在此处发布的原始代码。谢谢!
  • 当问题是由相关板条箱“引起”时,MRE 是否也适用? (rust_bert)——不管怎样,我现在加一个。一瞬间。 (编辑:即可以在 mre 中使用 rust_bert 吗?)
  • 可以在 mre 中使用 rust_bert — 是的,但是,就像我说的:箱子(及其版本)。该版本需要能够重现问题。

标签: rust rust-tokio


【解决方案1】:

解决问题

这是一个简化的例子:

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::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 提供了这样一个线程池:

#[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 这样的轻量级执行器来阻止结果:

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 运行时。理想情况下,如果库使用异步执行器,它也将提供直接异步功能,以便您可以使用自己的执行器。

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

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

struct Data;

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

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

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

【讨论】:

  • 这是否允许我在生成的线程中检索变量?
  • 根据“避免问题”/“查看 API 以查看是否有非阻塞替代方案”:所以这是 rust_bert 的问题?我做的第一件事是寻找一个异步版本,但没有。 (这种“需要”阻塞的东西——在我的情况下,其他一切都依赖于它)
  • @Mib“检索变量”不是一个定义明确的概念,所以我无法回答这个问题。如果您的意思是这样,您可以将值传递到线程并从线程中返回它们。
  • @Mib 在这种情况下,这不是“阻塞”的意思。当然,您有一组按特定顺序工作并且需要特定顺序的操作。如果您在等待更多输入时可以处理另一个序列,您不想阻止整个线程执行。在您的情况下,您可能会在一个线程上同时检索数百条推文。
  • 对,对不起:我需要在线程中运行的阻塞代码返回一个变量,我需要在生成的线程之外使用它。 — 我只做一次,其他一切都依赖于它,因此在开始获取推文之前我可以(并且想要)阻止。
【解决方案2】:

我在将 QA 模型加载到 warp(一个 tokio 运行时)时遇到了类似的问题,顺序运行时仍然不适合我,但我在 github issues of rust-bert 中找到了我的解决方案。解决方案是简单地将初始加载调用包含在task::spawn_blocking 中。这对我来说很好,因为无论如何在加载之前我都无法接受任何请求。一个sn-p下面,以防它帮助别人。

   78 fn with_model(
   79     qa: QaModel, // alias for Arc<Mutex<QuestionAnsweringModel>>
   80 ) -> impl Filter<Extract = (QaModel,), Error = std::convert::Infallible>       + Clone {
   81     warp::any().map(move || qa.clone())
   82 }
   83
   84 #[tokio::main]
   85 async fn main() {
   86     env_logger::init();
   87 
   88     // NOTE: have to download the model before booting up
>> 89     let qa_model: QaModel = task::spawn_blocking(move || {
   90         log::debug!("setting up qa model config");
   91         let c = qa_model_config();
   92         log::debug!("finished setting up qa model config");
   93 
   94         log::debug!("setting up qa model");
   95         let m = qa_model(c);
   96         log::debug!("finished setting up qa model");
   97         m
   98     })
   99     .await
  100     .expect("got model");
  101 
  102     let ask_handler = warp::path!("ask")
  103         .and(warp::get())
  104         .and(warp::query::<QaQuery>())
  105         .and(with_model(qa_model))
  106         .and_then(ask);
  107 
  108     warp::serve(ask_handler).run(([127, 0, 0, 1], 3030)).await;
  109 }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多