第二个 gtk_main_quit() 不工作

Second gtk_main_quit() not working

在应用程序中,我嵌套了 gtk 事件循环以便能够 return 回调中的值。 但是,我对已经调用 gtk_main_quit() 的回调有一些问题,因为对此函数的多次调用似乎没有退出尽可能多的事件循环嵌套。

这是我的问题的一个例子:

extern crate gtk;

use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::thread;

use gtk::{Button, ButtonExt, ContainerExt, Continue, Inhibit, WidgetExt, Window, WindowType};

fn main() {
    gtk::init().unwrap();
    let window = Window::new(WindowType::Toplevel);

    let quit = Arc::new(AtomicBool::new(false));

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });

    let button = Button::new_with_label("Click");
    let quit2 = quit.clone();
    button.connect_clicked(move |_| {
        let quit = quit2.clone();
        thread::spawn(move || {
            quit.store(true, Ordering::Relaxed);
        });
        println!("Run");
        gtk::main();
    });
    window.add(&button);
    window.show_all();

    gtk::idle_add(move || {
        if quit.load(Ordering::Relaxed) {
            println!("Quit");
            gtk::main_quit();
            gtk::main_quit();
            quit.store(false, Ordering::Relaxed);
        }
        Continue(true)
    });

    gtk::main();
}

正如您在 gtk::idle_add 调用中看到的那样,我调用了 gtk::main_quit() 两次,按下按钮时应退出应用程序,因为 gtk::main() 也被调用了两次(一次在main 函数结束,另一个在按钮点击回调中)。 但是当我点击按钮时应用程序没有退出。

gtk 的文档似乎表明这是预期的行为:

Makes the innermost invocation of the main loop return when it regains control.

(重点是我的)

因此,我相信这不会退出应用程序,因为调用 gtk::main_quit() 两次将不允许 gtk 主循环进入 "regain control".

我的问题是,在两次调用 gtk::main_quit() 之间我应该做什么来停止事件循环的 2 次嵌套?

我已经通过手动创建 glib MainLoop 并使用它而不是嵌套调用 gtk 事件循环来解决我的问题。

简短回答:解决方案是将第二个 gtk::main_quit 替换为:

gtk::idle_add(|| {
    gtk::main_quit();
    Continue(false)
});

和以前一样,第一个gtk::main_quit()将安排内部主循环退出。此外,空闲处理程序将由 outer 主循环拾取,导致它也立即终止。

这可以用一个伪递归函数来概括,该函数根据需要多次重复该过程:

fn deep_main_quit(n: usize) {
    if n == 0 {
        return;
    }
    gtk::main_quit();
    gtk::idle_add(move || {
        deep_main_quit(n - 1);
        Continue(false)
    });
}

请注意,使用 idle_add 不断检查标志将导致忙循环,您几乎肯定希望避免这种情况。 (在我的机器上,运行 你的程序占用了一个完整的 CPU 内核。)通常,首选方法是等待条件变量。但是如果您只需要告诉 GUI 线程做某事,如您的代码所示,您可以从不同的线程调用 glib::idle_add。提供的回调将在 GUI 线程中排队并执行。

有了这一变化,一个线程在 GUI 线程中释放两级 gtk_main 将如下所示:

fn main() {
    gtk::init().unwrap();
    let window = Window::new(WindowType::Toplevel);

    window.connect_delete_event(|_, _| {
        gtk::main_quit();
        Inhibit(false)
    });

    let button = Button::new_with_label("Click");
    button.connect_clicked(|_| {
        thread::spawn(|| {
            glib::idle_add(|| {
                println!("Quit");
                deep_main_quit(2);
                Continue(false)
            });
        });
        println!("Run");
        gtk::main();
    });
    window.add(&button);
    window.show_all();

    gtk::main();
}