【问题标题】:Vala force refresh progressbarVala 强制刷新进度条
【发布时间】:2017-11-22 10:13:05
【问题描述】:

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

我在此窗口中添加了一个进度条,以显示已处理的文件数量,但由于某种原因,它始终为空。 关于窗口的代码:

        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”方法,在该方法中我获取所选文件的路径并进行一些处理。

我正确地更新了进度条的分数,因为我添加了一些打印来了解值是否正确。只是窗口没有刷新,可能是因为它正在处理文件的所有工作。下面是 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 显示进度条的值正在更新,但在窗口中进度条始终为空。

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

【问题讨论】:

    标签: gtk progress-bar vala


    【解决方案1】:

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

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

    异步方法通过将代码与主循环完成的其他工作交错来避免这种情况。 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() 从您的点击处理程序中启动它。

    【讨论】:

    • 谢谢!您的解决方案非常有效。正如你所说,我更喜欢这样做而不是使用线程。
    【解决方案2】:

    除非您未显示某些相关代码,否则您将阻止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 整洁,但你实际上不必这样做。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-09-15
      • 1970-01-01
      • 2012-05-04
      • 1970-01-01
      • 2010-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多