从另一个线程或进程更新 Gtk.ProgressBar

Update a Gtk.ProgressBar from another thread or process

我有一个带有进度条的 GUI。它应该显示第二个线程所做的工作的进度。我希望线程可以在工作的每一步立即发送到 GUI 进度条之类的事件。但我不知道如何做到这一点。

Python 本身为线程情况提供了 Event class。但由于 Event.wait() 方法,它会阻塞 GUI 主线程。

如果第二个线程是一个进程,它如何改变这种情况和可能的解决方案?

我这里的示例基于 PyGObject (Pythons Gtk),但也与所有其他 GUI 库相关。 当前的解决方案有效,但 IMO 只是一种解决方法。 GUI(作为主线程)和第二个(辅助)线程通过线程安全 queue.Queue 共享数据。 GUI 线程中有一个计时器事件,检查 **fixed intervalls* 中的 qeueu 以获取来自线程的新数据并更新进度条。

#!/usr/bin/env python3
import time
import threading
import queue
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib


class MyThread(threading.Thread):
    def __init__(self, queue, n_tasks):
        threading.Thread.__init__(self)
        self._queue = queue
        self._max = n_tasks

    def run(self):
        for i in range(self._max):
            # simulate a task 
            time.sleep(1)
            # put something in the data queue
            self._queue.put(1)


class MyWindow(Gtk.Window):
    def __init__(self, n_tasks):
        Gtk.Window.__init__(self)

        # max and current number of tasks
        self._max = n_tasks
        self._curr = 0

        # queue to share data between threads
        self._queue = queue.Queue()

        # gui: progressbar
        self._bar = Gtk.ProgressBar(show_text=True)
        self.add(self._bar)
        self.connect('destroy', Gtk.main_quit)

        # install timer event to check the queue for new data from the thread
        GLib.timeout_add(interval=250, function=self._on_timer)
        # start the thread
        self._thread = MyThread(self._queue, self._max)
        self._thread.start()

    def _on_timer(self):
        # if the thread is dead and no more data available...
        if not self._thread.is_alive() and self._queue.empty():
            # ...end the timer
            return False

        # if data available
        while not self._queue.empty():
            # read data from the thread
            self._curr += self._queue.get()
            # update the progressbar
            self._bar.set_fraction(self._curr / self._max)

        # keep the timer alive
        return True

if __name__ == '__main__':
    win = MyWindow(30)
    win.show_all()
    Gtk.main()

您的实施是正确的。您正在处理线程命令,在队列中共享反馈,并通过 GLib.timeout_add().

从主循环更新进度条

最初,这似乎是一种执行简单进度条更新的复杂方法,但它是生成子进程并跟踪进度的唯一方法之一,同时尊重 Gtk 主循环。

根据我的问题的评论,我修改了我的例子。请谨慎使用,因为我仍然不清楚该解决方案是否线程安全。

我尝试了 GLib.idle_add() 并删除了我自己的计时器和队列。 注意:文档中signature/parameters的方法不正确。原来是

idle_add(function, *user_data, **kwargs)

线程的可能解决方案

#!/usr/bin/env python3
import time
import threading
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib


class MyThread(threading.Thread):
    def __init__(self, callback, n_tasks):
        threading.Thread.__init__(self)
        self._callback = callback
        self._max = n_tasks

    def run(self):
        for i in range(self._max):
            # simulate a task 
            time.sleep(1)
            # increment/update progress
            GLib.idle_add(self._callback)


class MyWindow(Gtk.Window):
    def __init__(self, n_tasks):
        Gtk.Window.__init__(self)

        # max and current number of tasks
        self._max = n_tasks
        self._curr = 0

        # gui: progressbar
        self._bar = Gtk.ProgressBar(show_text=True)
        self.add(self._bar)
        self.connect('destroy', Gtk.main_quit)

        # start the thread
        self._thread = MyThread(self._update_progress, self._max)
        self._thread.start()

    def _update_progress(self):
        # increment
        self._curr += 1
        # update the progressbar
        self._bar.set_fraction(self._curr / self._max)

        # end this event handler
        return False

if __name__ == '__main__':
    win = MyWindow(30)
    win.show_all()
    Gtk.main()

GLib.idle_add() 有什么作用?

我不是 Gtk 的专家或核心开发人员。根据我的理解,我会说您可以将 _install__ 事件处理程序方法放入 Gtk 主循环中。换句话说,第二个线程告诉 Gtk 主循环(这是第一个线程)在没有其他事情要做时调用 givin 方法(在 GUI 循环中经常退出)。

Process 没有解决方案,因为它们 运行 在单独的 Python 解释器实例中。无法在两个进程之间调用 GLib.idle_add()