首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >gtk-rs:如何从另一个线程更新视图

gtk-rs:如何从另一个线程更新视图
EN

Stack Overflow用户
提问于 2021-03-06 20:35:34
回答 1查看 1.2K关注 0票数 4

我正在用gtk-rs创建一个UI应用程序。在该应用程序中,我必须生成一个线程来不断地与另一个进程通信。有时,我必须根据线程中发生的事情来更新UI。但是,我不确定如何做到这一点,因为我无法跨线程保存对UI的任何部分的引用。

下面是我尝试过的代码:

代码语言:javascript
复制
use gtk;

fn main() {
    let application =
        gtk::Application::new(Some("com.github.gtk-rs.examples.basic"), Default::default()).unwrap()

    application.connect_activate(|app| {
        let ui_model = build_ui(app);
        setup(ui_model);
    });

    application.run(&[]);
}

struct UiModel { main_buffer: gtk::TextBuffer }

fn build_ui(application: &gtk::Application) -> UiModel {
    let glade_src = include_str!("test.glade");
    let builder = gtk::Builder::new();
    builder
        .add_from_string(glade_src)
        .expect("Couldn't add from string");

    let window: gtk::ApplicationWindow = builder.get_object("window").unwrap();
    window.set_application(Some(application));
    window.show_all();

    let main_text_view: gtk::TextView = builder.get_object("main_text_view")

    return UiModel {
        main_buffer: main_text_view.get_buffer().unwrap(),
    };
}

fn setup(ui: UiModel) {
    let child_process = Command::new("sh")
        .args(&["-c", "while true; do date; sleep 2; done"])
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();

    let incoming = child_process.stdout.unwrap();

    std::thread::spawn(move || {                              // <- This is the part to pay
        &BufReader::new(incoming).lines().for_each(|line| {   //    attention to.
            ui.main_buffer.set_text(&line.unwrap());          //    I am trying to update the
        });                                                   //    UI text from another thread.
    });
}

但是,我明白这个错误:

代码语言:javascript
复制
    |       std::thread::spawn(move || {
    |  _____^^^^^^^^^^^^^^^^^^_-
    | |     |
    | |     `*mut *mut gtk_sys::_GtkTextBufferPrivate` cannot be sent between threads safely

这说得通。我可以理解Gtk小部件并不是线程安全的。但我该如何更新呢?是否有一种安全地向UI线程发送信号的方法?还是有一种方法可以以不阻塞UI的方式在同一个线程中运行.lines().for_each(循环?

无论我采用什么解决方案,都必须有很高的性能。我将发送更多的数据比在这个例子,我想要一个非常低的延迟屏幕刷新。

谢谢你的帮忙!

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-03-07 18:14:25

好吧,我解决了问题。对于未来的任何人来说,这是一个解决方案。

glib::idle_add(|| {})允许您从UI线程上的另一个线程(thansk @Zan )运行一个闭包。这足以解决线程安全问题,但不足以绕过借款检查器。在线程之间发送没有一个GTKObject是安全的,因此另一个线程甚至不能保存对它的引用,即使它永远不会使用它。因此,您需要将UI引用全局存储在UI线程上,并在线程之间设置一个通信通道。以下是我一步一步地做的事情:

  1. 创建一种在不涉及传递闭包的线程之间发送数据的方法。我现在使用了std::sync::mpsc,但另一种选择可能更长远一些。
  2. 创建一些线程本地全局存储。在启动第二个线程之前,将UI引用和通信管道的接收端全局存储在主线程上。
  3. 通过闭包将通道的发送端传递给第二个线程。通过发送者传递您想要的数据。
  4. 传递完数据后,使用glib::idle_add() --不是带闭包,而是使用静态函数--告诉UI线程检查通道中的新消息。
  5. 在UI线程上的静态函数中,访问全局UI和接收变量并更新UI。

感谢这条线帮助我解决这个问题。这是我的代码:

代码语言:javascript
复制
extern crate gio;
extern crate gtk;
extern crate pango;

use gio::prelude::*;
use gtk::prelude::*;
use std::cell::RefCell;
use std::io::{BufRead, BufReader};
use std::process::{Command, Stdio};
use std::sync::mpsc;

fn main() {
    let application =
        gtk::Application::new(Some("com.github.gtk-rs.examples.basic"), Default::default())
            .unwrap();

    application.connect_activate(|app| {
        let ui_model = build_ui(app);
        setup(ui_model);
    });

    application.run(&[]);
}

struct UiModel {
    main_buffer: gtk::TextBuffer,
}

fn build_ui(application: &gtk::Application) -> UiModel {
    let glade_src = include_str!("test.glade");
    let builder = gtk::Builder::new();
    builder
        .add_from_string(glade_src)
        .expect("Couldn't add from string");

    let window: gtk::ApplicationWindow = builder.get_object("window").unwrap();
    window.set_application(Some(application));
    window.show_all();

    let main_text_view: gtk::TextView = builder.get_object("main_text_view").unwrap();

    return UiModel {
        main_buffer: main_text_view.get_buffer().unwrap(),
    };
}

fn setup(ui: UiModel) {
    let (tx, rx) = mpsc::channel();
    GLOBAL.with(|global| {
        *global.borrow_mut() = Some((ui, rx));
    });
    let child_process = Command::new("sh")
        .args(&["-c", "while true; do date; sleep 2; done"])
        .stdout(Stdio::piped())
        .spawn()
        .unwrap();

    let incoming = child_process.stdout.unwrap();

    std::thread::spawn(move || {
        &BufReader::new(incoming).lines().for_each(|line| {
            let data = line.unwrap();
            // send data through channel
            tx.send(data).unwrap();
            // then tell the UI thread to read from that channel
            glib::source::idle_add(|| {
                check_for_new_message();
                return glib::source::Continue(false);
            });
        });
    });
}

// global variable to store  the ui and an input channel
// on the main thread only
thread_local!(
    static GLOBAL: RefCell<Option<(UiModel, mpsc::Receiver<String>)>> = RefCell::new(None);
);

// function to check if a new message has been passed through the
// global receiver and, if so, add it to the UI.
fn check_for_new_message() {
    GLOBAL.with(|global| {
        if let Some((ui, rx)) = &*global.borrow() {
            let received: String = rx.recv().unwrap();
            ui.main_buffer.set_text(&received);
        }
    });
}
票数 6
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66510406

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档