Vala强制刷新进度条

Vala force refresh progressbar

我已经用 vala 做了一个应用程序,在某些时候我必须处理很多文件。我创建了一个 window 来选择一个文件夹,然后我获取文件的路径并对它们进行一些处理。

我在这个 window 中添加了一个进度条以显示已处理了多少文件,但由于某种原因它始终为空。 关于 window:

的代码
        this.files_window = new Gtk.Window();
        this.files_window.window_position = Gtk.WindowPosition.CENTER;
        this.files_window.destroy.connect (Gtk.main_quit);
        // VBox:
        Gtk.Box vbox = new Gtk.Box (Gtk.Orientation.VERTICAL, 5);
        this.files_window.add (vbox);
        // Buttons to open and close
        Gtk.Button cancel = new Gtk.Button.with_label ("Cancel");
        Gtk.Button select = new Gtk.Button.with_label ("Select");
        vbox.add (select);
        vbox.add (cancel);
        // proogress bar
        this.progress_bar = new Gtk.ProgressBar();
        vbox.add(this.progress_bar);
        // conect select to method do_stuff
        select.clicked.connect (do_stuff);
        this.files_window.show_all ();

如您所见,我将按钮 "select" 连接到方法 "do_stuff",在该方法中我获取所选文件的路径并进行一些处理。

我正确更新了进度条的分数,因为我添加了一些打印件以了解该值是否正确。只是 windows 没有刷新,可能是因为它正在处理文件的所有工作。这是关于 do_stuff() 方法的代码:

       // some proces to get paths of files in the list sfiles
       double fraction = 0.0;
       this.progress_bar.set_fraction (fraction);
       int processed_files = 0;
       foreach (string sfile in sfiles) {
            do_some_proces_to_file(sfile);
            processed_files += 1;
            fraction = (double)processed_files/(double)sfiles.length;
            this.progress_bar.set_fraction (fraction);
            stdout.printf("Real fraction: %f\n", this.progress_bar.get_fraction());
        }

printf 显示进度条的值正在更新,但在 window 中进度条始终为空。

我错过了什么吗?这是做进度条的正确方法吗?我应该创建另一个线程来做这些事情吗?

除非您没有显示某些相关代码,否则您会阻止 main loop。一种选择是在线程中执行所有操作,并使用空闲回调来更新 UI。基本思想是这样的:

new GLib.Thread<void*>("file-processor", () => {
  foreach (string sfile in sfiles) {
    /* do stuff */
    GLib.Idle.add(() => {
      /* Update progress */
      return false;
    });
  }
  return null;
});

根据您的应用程序,您可能需要添加互斥体以避免竞争条件。您可能还需要添加一些取消操作的逻辑。

更好的选择可能是使用 GLib.ThreadPool。您仍然希望从空闲回调中更新 UI,但这将允许每个任务并行执行,这可以提供显着的加速。

如果我是你,我可能会把它全部打包在一个 async function 中以保持 API 整洁,但你真的不必这样做。

正如@nemequ 所说,您的代码阻塞了主循环线程(它处理用户输入和 scheduling/drawing 小部件更新),因此在方法完成之前进度条不会更新。

使用线程是解决问题的一种方法,但是使用线程会导致很多错误,因为线程之间即使是简单的交互也很难安全。

异步方法通过将代码与主循环完成的其他工作交织来避免这种情况。 do_stuff() 的异步版本编写起来非常简单,只需将其声明为异步并在某处的 for 循环中放置一个 yield:

public async void do_stuff() {
    ...
    foreach (string sfile in sfiles) {
        // all of this is as before
        do_some_proces_to_file(sfile);
        processed_files += 1;
        fraction = (double)processed_files/(double)sfiles.length;
        this.progress_bar.set_fraction (fraction);

        // Schedule the method to resume when idle, then
        // yield control back to the caller
        Idle.add(do_stuff.callback);
        yield;
    }
}

然后您可以通过调用以下命令从您的点击处理程序中启动它:do_stuff.begin()