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()
。
我已经用 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()
。