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

问题描述 投票:2回答:2

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

Python本身为线程情况提供了Event类。但是由于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()
python multithreading gtk pygobject
2个回答
1
投票

您的实施是正确的。您正在处理线程命令,在队列中共享反馈,并通过GLib.timeout_add()从主循环更新进度条。

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


0
投票

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

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

idle_add(function, *user_data, **kwargs)

Threads的可能解决方案

#!/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__事件处理程序metode进入Gtk主循环。换句话说,第二个线程告诉Gtk主循环(这是第一个线程),当没有其他东西是todo时调用givin方法(这在GUI循环中经常退出)。

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

© www.soinside.com 2019 - 2024. All rights reserved.