【问题标题】:How can one update GTK+ UI in Vala from a long operation without blocking the UI如何在不阻塞 UI 的情况下从长时间操作中更新 Vala 中的 GTK+ UI
【发布时间】:2016-09-15 02:09:56
【问题描述】:

当我使用此页面中的任何代码而不进行任何修改时:https://wiki.gnome.org/Projects/Vala/AsyncSamples

我总是得到: warning: ‘g_simple_async_result_new’ is deprecated: Use 'g_task_new' instead.

所以我继续建议使用GTask。但是,当我尝试在 Vala 中使用 GLib.Task 时,我只是在声明一个任务时遇到了困难。因此,我没有在我自己的代码中使用来自 GIO 的异步,因为它已被弃用,我尝试使用 GLib.Task 来简单地使用 for 循环中的数字更新 Gtk 按钮的标签,这样代码如下所示:

using Gtk;
Button button;

public static int main (string[] args) {
    Gtk.init (ref args);

    var window = new Window ();
    window.title = "Count without blocking the UI";
    window.border_width = 10;
    window.window_position = WindowPosition.CENTER;
    window.set_default_size (350, 70);
    window.destroy.connect (Gtk.main_quit);
    button = new Button.with_label ("Start counting");
    button.clicked.connect (() => {
    GLib.Task task = new GLib.Task(button, new Cancellable());
});

window.add (button);
window.show_all ();

Gtk.main ();

return 0;
}

void count(){
    for(int i = 0; i < 10000; i++){
        button.label = i.to_string();
    }
}

但编译时我得到:error: ‘_data_’ undeclared (first use in this function) _tmp3_ = g_task_new_finish (_data_->_res_);

第 15 行是导致编译器抛出该错误的原因。它来自vala编译器生成的C代码。

我发现的主要问题是 Vala 中的 GTask 构造函数签名与 C 不同。因此,我无法重新创建此处找到的代码:GUI becomes unresponsive after clicking the button using GTK+ in C

因为对于初学者来说,我不允许将两个以上的参数传递给 GLib.Task 对象构造函数。 Task 对象的构造函数在每种语言中都不同。 Vala中GLib.Task的构造函数可以在here找到。

因此我的问题是:

有没有关于如何在 Vala 中使用 GLib 任务 (GTask) 来执行更新 UI 而不阻塞它的操作的示例?如果没有,是否有另一种方法来更新 UI 而不会阻止它?一种不被弃用的方式?

谢谢。

P.S:我已经尝试过 GLib.Thread、GLib.ThreadPool 和 GLib.Idle。它们都在 for 循环中阻塞了 UI。 GLib.Idle 不会完全阻塞 UI,但它会呈现错误,因为在循环运行时响应用户输入变得非常缓慢。

【问题讨论】:

  • 尽快更新 UI 几乎肯定会在单线程模型中阻止它。您可以进行更重要的计算,也可以在我的回答中的循环中使用GLib.Timeout
  • 我不太确定我是否了解那里发生的情况以及该补丁的功效。我有一个较新版本的 glib: 2.48.1 并且无论在我的代码中使用 --target-glib=2.48.1 和 valac,我仍然会收到警告。我认为临时解决方案不起作用。

标签: multithreading gtk3 vala


【解决方案1】:

使用 async 完全没问题,还有 some work already 用于移植当前代码以使用 GTask。

你的计数代码是阻塞的,所以即使它的执行被 GTask 缓冲,它仍然会阻塞 UI。

执行 CPU 密集型后台操作的正确方法是异步使用子进程或在线程中启动工作并在主循环中调度。

async void test_async () {
    new Thread<void> (() => {
         // count here...
        test_async.callback ();
    });
    yield;
}

GTask 或更一般的 GAsyncResult 只提供一个容器来保存异步操作的结果。他们还建议使用 GThreadPool,但它有点样板。

另一个有趣的事情是test_async.callback实际上是一个SourceFunc,所以你可以在GLib.Timeout中传递它。

编辑:

为了更适合您的问题,如果您想在 UI 进行时更新,请使用异步循环:

async test_callback () {
    for (var i = 0; i < 10000; i++) {
        button.label = i.to_string ();
        Idle.add (test_async.callback);
        yield; // pause execution until retriggered in idle
    }
}

这是一个完整且有效的示例:

using Gtk;
Button button;

public static int main (string[] args) {
    Gtk.init (ref args);

    var window = new Window ();
    window.title = "Count without blocking the UI";
    window.border_width = 10;
    window.window_position = WindowPosition.CENTER;
    window.set_default_size (350, 70);
    window.destroy.connect (Gtk.main_quit);
    button = new Button.with_label ("Start counting");
    button.clicked.connect (() => {
        count ();
    });

    window.add (button);
    window.show_all ();

    Gtk.main ();

    return 0;
}

async void count(){
    for(int i = 0; i < 10000; i++){
        button.label = i.to_string();
        Idle.add (count.callback);
        yield;
    }
}

【讨论】:

  • 谢谢。我将其标记为解决方案,但我仍然收到相同的警告。此外,编译器说,第一个代码剪辑不允许在那里使用 lambda 表达式。第二个需要小修。应该是:“async void test_callback(){...”和“Idle.add(test_callback.callback);”以这种方式使其编译良好并执行我打算执行的操作。虽然警告仍然出现。
  • 引起我注意的是,在 valadoc 中,“Idle.add ()”方法签名与您的不匹配,但您的编译正常。我猜 valadoc 文档已经过时了。尽管我仍然收到在您的代码中使用 GTask 的警告。我希望这个问题能在未来得到解决,因为当前的补丁不起作用,我使用的是 valac 版本 0.32.1 和 glib-2.0 版本 2.48.1,即使使用显式命令“--target-glib”我仍然会收到警告[切换]”。
  • 您仍然会收到 GTask 的警告,因为补丁尚未合并。如果你不使用异步,你最终会得到很多样板。
  • @arteymix 为什么没有在此处包含完整的示例代码?
  • @JensMühlenhoff 抱歉,我还需要吃更多的 StackOverflow 外壳,我认为最好不要重新发布整个示例并稍作改动。
猜你喜欢
  • 2021-10-10
  • 1970-01-01
  • 1970-01-01
  • 2011-09-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-15
  • 2012-09-15
相关资源
最近更新 更多