Gtk-rs button click: there is no reactor running, must be called from the context of a Tokio 1.x runtime

Hello

This question is a follow-up question of this one: Using Gtk-rs and Tokio - #4 by alice

I am able to compile my code and create a basic form in Gtk-rs. But when I click the submit-button of my form I want to call a async function to send a POST-request to my server. Clicking the button currently gives the following runtime-error:

thread 'main' panicked at 'there is no reactor running, must be called from the context of a Tokio 1.x runtime', src/main.rs:94:9
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

This is my main.rs


fn main() {
    let rt = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(2) // Set the number of Tokio worker threads
        .enable_all()
        .build()
        .unwrap();
    std::thread::scope(|s| {
        s.spawn(|| {
            rt.block_on(initialize_tokio());
        });

        gtk();
    })
}

fn gtk() {
     gtk::init().expect("Failed to initialize GTK.");
    let app = gtk::Application::builder().application_id("123").build();

    // Connect to "activate" signal of `app`
    app.connect_activate(build_ui);

    // Run the application
    app.run();
}

fn build_ui(app: &gtk::Application) {
    // ...
    // Making inputs and button
    
    let username = username_input.text().as_str().to_string();
    let password = password_input.text().as_str().to_string();

    // When button is clicked call async function login
    login_button.connect_clicked(move | _button| {
        let _username = username.clone();
        let _password = password.clone();
        tokio::runtime::Handle::current().spawn(async move {
            server::login(_username.to_string(), _password.to_string()).await;
        });
    });
    
     // ...
     // Build and present window
}

async fn initialize_tokio() {
     // Doing things in Tokio like calling a Websocket (Works)
}


I tried tokio::runtime::Handle::current() because I thought that would retrieve the active Tokio-runtime on which I could spawn.

How would I fix this?

I was able to fix it to edit fn build_ui like this:

   // New tokio runtime
    let tokio_runtime = tokio::runtime::Runtime::new().expect("Failed to create Tokio runtime");

    let username = username_input.text().as_str().to_string();
    let password = password_input.text().as_str().to_string();
    login_button.connect_clicked(move | _button| {
        let _username = username.clone();
        let _password = password.clone();
        tokio_runtime.spawn(async move {
            server::login(_username.to_string(), _password.to_string()).await;
        });
    });

I just initialized a new Tokio Runtime. It works perfectly but I don't know if this is the best solution?

you code uses block_on, so the runtime is only available to the future passed to block_on.

if you want the runtime be available to the thread, use enter().

    let _guard = rt.enter();
    let current = Handle::current();
    let join_handle = handle.spawn(some_future);
    // or just
    let join_handle = tokio::spawn(some_future);

please note, don''t assign the guard to _ like this:

let _ = rt.enter();
2 Likes

So like this?

fn main() {
    let rt = tokio::runtime::Builder::new_multi_thread()
        .worker_threads(2) // Set the number of Tokio worker threads
        .enable_all()
        .build()
        .unwrap();
            
        let _guard = rt.enter();
        let join_handle = tokio::spawn(initialize_tokio());
        
        gtk();
}


    login_button.connect_clicked(move | _button| {
        let username = username_input.text().as_str().to_string();
        let password = password_input.text().as_str().to_string();
        tokio::spawn(async move {
            match server::login(username, password).await {
                true => println!("success"),
                false => println!("failure"),
            };
        });
    });

Seems to work, thanks!

if you don't need fine control over tokio runtime, you can simply use the tokio::main attributes and make your main function async, like this:

#[tokio::main(worker_threads = 2)]
async fn main() {
    // `Handle::current()` guaranteed to not panic
    let current = Handle::current();
    // `block_on` is meaningless in async context, just `await`
    initialize_tokio().await;
    // you can `spawn` tasks without awaiting the result
    let join_handle = tokio::spawn(some_background_task);
}
1 Like

Thanks for the help mate!