gpt4 book ai didi

multithreading - gtk-rs:如何从另一个线程更新 View

转载 作者:行者123 更新时间:2023-12-03 11:43:48 24 4
gpt4 key购买 nike

我正在使用gtk-rs创建一个UI应用程序。在该应用程序中,我必须产生一个线程以与另一个进程持续通信。有时,我必须根据该线程中发生的情况来更新UI。但是,我不确定如何执行此操作,因为我无法跨线程持有对UI的任何部分的引用。
这是我尝试的代码:

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.
});
}

但是,我得到了错误:
    |       std::thread::spawn(move || {
| _____^^^^^^^^^^^^^^^^^^_-
| | |
| | `*mut *mut gtk_sys::_GtkTextBufferPrivate` cannot be sent between threads safely
这是有道理的。我知道Gtk小部件不是线程安全的。但是那我该如何更新呢?有没有一种方法可以安全地将信号发送到UI线程?还是有一种方法可以在不阻塞UI的情况下在同一线程中运行 .lines().for_each(循环?
无论我采用哪种解决方案,都必须具有非常高的性能。我将发送比示例中更多的数据,并且我希望屏幕延迟非常低。
谢谢你的帮助!

最佳答案

好的,我解决了问题。对于将来的任何人,这都是解决方案。glib::idle_add(|| {})使您可以从UI线程上的另一个线程(感谢@Zan Lynx)运行闭包。这足以解决线程安全问题,但还不足以解决借阅检查器。没有GTKObject在线程之间发送是安全的,因此即使另一个线程永远不会使用它,它也永远无法保存对其的引用。因此,您需要将UI引用全局存储在UI线程上,并在线程之间建立通信 channel 。这是我逐步执行的操作:

  • 创建一种在线程之间发送数据的方法,该方法不涉及传递闭包。我现在使用了std::sync::mpsc,但长期使用另一种方法可能更好。
  • 创建一些线程本地全局存储。在开始第二个线程之前,请在主线程上全局存储UI引用和该通信管道的接收端。
  • 通过闭包将 channel 的发送端传递到第二个线程。通过该发件人传递所需的数据。
  • 传递数据后,请使用glib::idle_add()(不带闭包,而是带静态函数)告诉UI线程在 channel 中检查新消息。
  • 在UI线程上的该静态函数中,访问全局UI和接收器变量并更新UI。

  • 感谢 this thread帮助我解决了这一问题。这是我的代码:
    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);
    }
    });
    }

    关于multithreading - gtk-rs:如何从另一个线程更新 View ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66510406/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com