如何在不阻塞 UI 的情况下通过长时间操作更新 Vala 中的 GTK+ UI

How can one update GTK+ UI in Vala from a long operation without blocking the UI

当我使用此页面中的任何代码而不进行任何修改时: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 不同。因此,我无法重新创建此处找到的代码:

因为对于初学者来说,我不允许将两个以上的参数传递给 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,但它会在某种意义上呈现错误,即在循环 运行.

时响应用户输入变得非常慢

使用 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;
    }
}