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

Multithreading gtk rs:如何从另一个线程更新视图,multithreading,rust,gtk3,gtk-rs,Multithreading,Rust,Gtk3,Gtk Rs,我正在使用创建一个UI应用程序。在该应用程序中,我必须生成一个线程来与另一个进程持续通信。有时,我必须根据线程中发生的情况更新UI。但是,我不知道该怎么做,因为我无法跨线程保存对UI任何部分的引用 以下是我尝试的代码: 使用gtk; fn main(){ 让申请= gtk::Application::new(一些(“com.github.gtk-rs.examples.basic”),Default::Default()).unwrap() 应用程序。连接并激活(|应用程序|{ 让ui\u mo

我正在使用创建一个UI应用程序。在该应用程序中,我必须生成一个线程来与另一个进程持续通信。有时,我必须根据线程中发生的情况更新UI。但是,我不知道该怎么做,因为我无法跨线程保存对UI任何部分的引用

以下是我尝试的代码:

使用gtk;
fn main(){
让申请=
gtk::Application::new(一些(“com.github.gtk-rs.examples.basic”),Default::Default()).unwrap()
应用程序。连接并激活(|应用程序|{
让ui\u model=build\u ui(app);
设置(ui_模型);
});
运行(&[]);
}
结构UiModel{main_buffer:gtk::TextBuffer}
fn构建ui(应用程序:>k::应用程序)->UiModel{
让glade_src=包含_str!(“test.glade”);
让builder=gtk::builder::new();
建设者
.add_from_string(glade_src)
.expect(“无法从字符串添加”);
让窗口:gtk::ApplicationWindow=builder.get_对象(“窗口”).unwrap();
设置应用程序(一些(应用程序));
window.show_all();
让main_text_视图:gtk::TextView=builder.get_对象(“main_text_视图”)
返回模型{
主缓冲区:主文本视图。获取缓冲区()。展开(),
};
}
fn设置(ui:UiModel){
让子进程=命令::新建(“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 |{/好的,我解决了这个问题。对于未来的任何人,这里是解决方案

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

  • 创建一种不涉及传递闭包的线程间发送数据的方法。我现在使用了
    std::sync::mpsc
    ,但从长远来看,另一种方法可能更好
  • 创建一些线程本地全局存储。在启动第二个线程之前,将UI引用和该通信管道的接收端全局存储在主线程上
  • 通过闭包将通道的发送端传递给第二个线程。通过该发送方传递所需的数据
  • 在传递数据之后,使用glib::idle_add()——不是使用闭包,而是使用静态函数——告诉UI线程检查通道中的新消息
  • 在UI线程上的静态函数中,访问全局UI和接收器变量并更新UI
  • 感谢您帮助我解决这个问题。以下是我的代码:

    外部板条箱gio;
    外部板条箱gtk;
    外部板条箱盘古;
    使用gio::前奏::*;
    使用gtk::前奏::*;
    使用std::cell::RefCell;
    使用std::io::{BufRead,BufReader};
    使用std::process::{Command,Stdio};
    使用std::sync::mpsc;
    fn main(){
    让申请=
    gtk::Application::new(一些(“com.github.gtk-rs.examples.basic”),Default::Default()
    .unwrap();
    应用程序。连接并激活(|应用程序|{
    让ui\u model=build\u ui(app);
    设置(ui_模型);
    });
    运行(&[]);
    }
    结构模型{
    主缓冲区:gtk::TextBuffer,
    }
    fn构建ui(应用程序:>k::应用程序)->UiModel{
    让glade_src=包含_str!(“test.glade”);
    让builder=gtk::builder::new();
    建设者
    .add_from_string(glade_src)
    .expect(“无法从字符串添加”);
    让窗口:gtk::ApplicationWindow=builder.get_对象(“窗口”).unwrap();
    设置应用程序(一些(应用程序));
    window.show_all();
    让main_text_视图:gtk::TextView=builder.get_对象(“main_text_视图”).unwrap();
    返回模型{
    主缓冲区:主文本视图。获取缓冲区()。展开(),
    };
    }
    fn设置(ui:UiModel){
    let(tx,rx)=mpsc::channel();
    全局。带有(|全局|{
    *global.borrow_mut()=Some((ui,rx));
    });
    让子进程=命令::新建(“sh”)
    .args(&[“-c”,“while true;do date;sleep 2;done”])
    .stdout(Stdio::piped())
    .spawn()
    .unwrap();
    let incoming=child_process.stdout.unwrap();
    std::thread::spawn(移动| |){
    &BufReader::新的(传入的).lines()。用于每个(|行|{
    让data=line.unwrap();
    //通过信道发送数据
    发送(数据).unwrap();
    //然后告诉UI线程从该通道读取
    glib::source::idle|u add(| |{
    检查是否有新消息();
    返回glib::source::Continue(false);
    });
    });
    });
    }
    //用于存储ui和输入通道的全局变量
    //仅在主线程上
    本地线程(
    静态全局:RefCell=RefCell::new(无);
    );
    //函数检查新消息是否已通过
    //全局接收器,如果是,则将其添加到UI。
    fn检查是否有新消息(){
    全局。带有(|全局|{
    如果让一些((ui,rx))=&*global.borrow(){
    let received:String=rx.recv().unwrap();
    ui.main\u buffer.set\u text(&received);
    }
    });
    }
    
    我在Rust中没有这样做,因此一些常规的建议是:如果你有一个线程运行速度快且数据量大,那么你可能无法轻松为GUI创建数据快照。因此,请确保你可以将其分块锁定,这样工作线程就不会经常被阻塞。然后,我会做你在GUI中需要做的事情,只读取项目的数据显示在屏幕上。不要创建所有数据的列表或表格视图。创建一个带有滚动条的视图,滚动条显示模糊的图像
        |       std::thread::spawn(move || {
        |  _____^^^^^^^^^^^^^^^^^^_-
        | |     |
        | |     `*mut *mut gtk_sys::_GtkTextBufferPrivate` cannot be sent between threads safely