2017-09-26 66 views
0

我已经与vala做了一个应用,在某些时候我必须处理大量文件。我创建了一个窗口来选择文件夹,然后获取文件的路径并对它们进行一些处理。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(); 

正如你所看到的,我连接按钮“选择”方法“do_stuff”从哪里获得所选文件的路径,并提出一些过程。

我更新correctlly的进步党栏的分数,因为我已经添加了一些版画知道,如果值是正确的,它是。只是这些窗户并不令人耳目一新,可能是因为它在处理文件的过程中所做的所有工作。这里是关于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的表明PROGRES栏的值被更新,但在窗口杆总是空的。

我错过了什么吗?这是进行进度栏的正确方法吗?我应该做另一个线程来做这些东西吗?

回答

1

作为@nemequ说,您的代码阻塞主循环线程(其可以处理用户输入和调度/绘图插件更新),因此它不被更新进度条,直到方法完成。

使用一个线程是一个办法解决这个问题,但是然而使用线程可以导致很多的错误,因为它可能很难线程安全之间作出即使是简单的交互。

异步方法通过将代码与主循环完成的其他工作交错来避免这种情况。您do_stuff()的异步版本将是相当直着写,只是声明它异步,把一个产量在for循环的地方:

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()

+0

谢谢!您的解决方案非常完美。正如你所说,我宁愿这样做,而不愿意使用线程。 – bcedu

1

除非有你不表现出一些相关的代码,你挡住了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整洁,但你真的没有来。