Обновите Gtk.ProgressBar из другого потока или процесса - PullRequest
2 голосов
/ 26 апреля 2019

У меня есть графический интерфейс с прогрессбаром. Он должен показывать ход работы второй ветки. Я хотел бы иметь что-то похожее на событие, которое поток может отправлять на панель прогресса графического интерфейса сразу на каждом этапе работы. Но я не понимаю, как это можно сделать.

Сам Python предлагает класс Event для многопоточности. Но он заблокирует основной поток графического интерфейса из-за метода Event.wait().

Как это меняет ситуацию и возможные решения, если второй поток является процессом?

Мой пример здесь основан на PyGObject (Pythons Gtk), но также связан со всеми другими библиотеками GUI. Текущее решение работает, но это всего лишь обходной путь IMO. Графический интерфейс (как основной поток) и второй (рабочий) поток, совместно использующий данные, через безопасный поток queue.Queue. В потоке графического интерфейса есть событие таймера, проверяющее 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()

Ответы [ 2 ]

1 голос
/ 28 апреля 2019

Ваша реализация верна. Вы обрабатываете многопоточные команды, делитесь отзывами в очереди и обновляете индикатор выполнения из основного цикла с помощью GLib.timeout_add().

Изначально это может показаться сложным способом простого обновления индикатора выполнения, но это один из немногих способов порождать дочерний процесс и отслеживать прогресс при соблюдении основного цикла Gtk.

0 голосов
/ 30 апреля 2019

На основании комментариев к моему вопросу я изменил свой пример.Пожалуйста, используйте это с осторожностью, потому что мне все еще неясно, является ли решение поточно-ориентированным или нет.

Я попробовал 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 (который является первым потоком) вызвать метод givin, когда больше ничего не нужно (это часто завершается в цикле GUI).

Существует нет решения для процесса , потому что они запускаются в отдельном экземпляре интерпретатора Python.Невозможно вызвать GLib.idle_add() между двумя процессами.

...