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

7
我有一个带有进度条的 GUI,它应该显示第二个线程完成工作的进度。我希望有像事件一样的东西,线程可以在每一步工作后立即发送给GUI的进度条。但我不知道怎么做。
Python本身提供了一个用于线程情况的 "Event" 类。但由于 "Event.wait()" 方法,它将阻塞 GUI 主线程。
如果第二个线程是一个进程,它如何改变情况和可能的解决方案?
我的例子基于 PyGObject(Python 的 Gtk),但与所有其他 GUI 库相关。当前的解决方案虽然可行,但我认为它只是一个解决方法。GUI(作为主线程)和第二个(工作)线程通过线程安全的 "queue.Queue" 共享数据。GUI 线程中有一个定时器事件,在**固定时间间隔内**检查队列中是否有来自线程的新数据,并更新进度条。
#!/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()

1
对我来说,这是正确的解决方案,而不是权宜之计。 - furas
1
尝试使用 MyThread(self._bar, self._max),并在 def run(self): ... self.bar.set_fraction(... 中使用。 - stovfl
1
@buhtz:“线程不安全?”:定义“线程不安全”?只要您的Thread唯一一个调用self.bar.set_fraction(...)的线程,就不会产生冲突。试试吧,你要么得到一个错误,要么它可以工作。 - stovfl
1
通过线程更新小部件的不好之处在于它会随机导致分段错误和其他噩梦。查阅GUI主循环相关知识。 - theGtknerd
1
@buhtz: " don't get the essence": GLib.idle_add(update_progress, i) 的使用方式类似于 self._queue.put(1)。函数 def example_target() 相当于你的 def run(self):,在一个线程中运行。 - stovfl
显示剩余5条评论
2个回答

4

根据我提出问题时的评论,我修改了我的示例。请谨慎使用此示例,因为我仍然不确定解决方案是否线程安全。

我尝试了 GLib.idle_add() 并删除了自己的计时器和队列。注意:文档关于方法的签名/参数不正确。最初它是这样的。

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的专家或核心开发人员。据我理解,你可以将事件处理方法安装到Gtk主循环中。换句话说,第二个线程告诉Gtk主循环(即第一个线程)在没有其他事情要做时调用给定的方法(这在GUI循环中经常发生)。

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


1
“文档对于签名 / 参数并不正确”:您可以使用 GLib.idle_add(<ProgressBar引用> .set_fraction,fraction),这将使 def _update_progress(self): 不再需要。 - stovfl

3
你的实现是正确的。你正在处理线程命令,将反馈信息共享在队列中,并通过GLib.timeout_add()从主循环更新进度条。
最初的回答可能会让人觉得这是一种复杂的方式来更新简单的进度条,但这是唯一一种可以生成子进程并跟踪进度的方法,同时尊重Gtk主循环。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接