#gtk #thread #widgets #modifing

gtk-fnonce-on-eventloop

允许从其他线程修改 gtk-rs 小部件

2 个版本

使用旧的 Rust 2015

0.2.1 2018 年 11 月 17 日
0.2.0 2018 年 11 月 17 日

GUI 中排名 494

MIT 许可证

26KB
155

从其他线程修改 gtk 小部件

reddit 问题

在稳定版本上运行。

这不是最快的实现,但对于几乎所有用例来说,这应该足够了。

Cargo.toml

你可以使用最新版本,但需要在 gtk 旁边安装 glib。

[dependencies]
gtk-fnonce-on-eventloop = "0.2"

此 crate 使用以下依赖项

  • 版本 = "0.5.0" 且具有功能 = ["v3_10"] 的 gtk
  • 版本 = "0.6.0" 的 glib

示例用法

使用此方法归结为

  • 调用 gtk_refs! 宏。
  • 在启动事件循环之前调用 init_storage()
  • 在其他线程中调用 do_in_gtk_eventloop() 来修改小部件

不要在主线程中调用 do_in_gtk_eventloop(),因为这会导致阻塞。

    gtk_refs!(
        pub mod widgets;                // The macro emits a new module with this name
        struct WidgetRefs;              // The macro emits a struct with this name containing:
        main_window : gtk::Window ,     // widget_name : Widgettype
        button1 : gtk::Button           // ..
    );

    fn main() {

        if gtk::init().is_err() {
            println!("Failed to initialize GTK.");
            return;
        }

        let window = Window::new(WindowType::Toplevel);
        window.set_title("gtk-fnonce-on-eventloop Example Program");
        window.set_default_size(350, 70);
        let button = Button::new_with_label("Spawn another thread!");
        window.add(&button);
        window.show_all();

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

        button.connect_clicked(|_| {
            std::thread::spawn(some_workfunction);
            println!("Clicked!");
        });

        // You need the following two statements to prepare the
        // static storage needed for cross thread access.
        // See the `from_glade.rs` example for a more elegant solution
        let widget_references = widgets::WidgetRefs {
            main_window: window.clone(),
            button1:     button.clone(),
        };

        widgets::init_storage(widget_references);
        // End

        // This type has a function for each of your widgets.
        // These functions return a clone() of the widget.
        window.show_all();

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

        // Start event loop
        gtk::main();
    }

    fn compute() {
        use std::thread::sleep;
        use std::time::Duration;
        sleep(Duration::from_secs(1));
    }

    fn some_workfunction()  {
        let mut i = 0;

        loop {
            compute();

            i += 1;
            let text = format!("Round {} in {:?}", i, std::thread::current().id());

            widgets::do_in_gtk_eventloop(|refs| {
                refs.button1().set_label(&text);
            });
        }
    }

该宏生成以下代码

    pub mod widgets {
        pub struct WidgetRefs {
            pub main_window : gtk::Window,
            ...
        }
        impl From<&gtk::Builder> for WidgetRefs { ... };
        impl WidgetRefs {
            fn main_window() -> gtk::Window { } // returns a .clone() of the widget
            ...
        }

        pub fn init_storage(WidgetRefs);
        pub fn init_storage_from_builder(&gtk::Builder);
        pub fn do_in_gtk_eventloop( FnOnce(Rc<WidgetRefs>) );
    }

它是如何工作的?

  • 您提供给 do_in_gtk_eventloop(闭包) 的闭包将通过 glib::idle_add() 在 gtk 事件循环中执行。
  • 在调用站点 do_in_gtk_eventloop() 将等待闭包运行。
  • 来自多个线程的闭包将始终按顺序运行。
  • 如果闭包崩溃,do_in_gtk_eventloop() 也会崩溃。您可能看不到崩溃,因为进程通常退出得太快。

如果您想:请参阅示例文件夹 - 在结构体中使用额外的非发送字段(除了小部件引用之外的其他东西) - 使用 glade

实现是如何工作的?

+---------------------------------+   +----------------------------------+
|GTK event loop thread            |   |Global statics                    |
|                                 |   |                                  |
|  +----------------------------+ |   |  TX : Sender<(Fn, Cb)>           |
|  |Thread local statics        | |   |                                  |
|  |                            | |   |                                  |
|  |  DATA : Non-Send Refereces | |   +----------------------------------+
|  |  RX : Receiver<(Fn, Cb)>   | |
|  |                            | |   +----------------------------------+
|  +----------------------------+ |   |Some other thread                 |
|                                 |   |                                  |
|                                 |   |  do some stuff                   |
|  +---------------------------+  |   |                                  |
|  |event loop()               |  |   |  call do_in_gtk_eventloop(Fn)    |
|  |                           |  |   |    This Fn has access to DATA    |
|  |  +----------------------+ |  |   |                                  |
|  |  |closure added with    | |  |   |                                  |
|  |  |idle_add() to execute | |  |   +----------------------------------+
|  |  |on the gtk thread {   | |  |
|  |  |                      <-----------------------------------------------+
|  |  |  Pop (Fn,Cb) from RX | |  |                                          |
|  |  |  Call Fn(DATA)       | |  |                                          |
|  |  |  Signal end of Fn    | |  |   +----------------------------------+   |
|  |  |   via Cb             | |  |   |do_in_gtk_eventloop(Fn)           |   |
|  |  |                      | |  |   |                                  |   |
|  |  |                      | |  |   |  Box closure Fn and transmute    |   |
|  |  +----------------------+ |  |   |   livetime to 'static            |   |
|  |                           |  |   |                                  |   |
|  +---------------------------+  |   |  Create a signal Cb              |   |
|                                 |   |  Push (boxed Fn, Cb) to TX       |   |
+---------------------------------+   |  Add this closure via idle_add() +---+
                                      |  Wait for the signal Cb          |
                                      |  return                          |
                                      |                                  |
                                      |                                  |
                                      +----------------------------------+

init_storage() 初始化 DATA、RX 和 TX。

使用 unsafe

有一个仅用于便利的 unsafe 使用。它允许闭包引用局部堆栈,而不是要求闭包上的 'static。

您可以轻松地删除 unsafe,但那时您被迫将所有内容都移动到 with_ref 闭包中。

依赖项

~13MB
~314K SLoC