обновление qt GUI сообщениями, сгенерированными мультипроцессором. Процесс - PullRequest
0 голосов
/ 28 апреля 2020

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

Следующий код не обновляет до тех пор, пока не закончится полный процесс, поэтому я вижу только последнее сообщение. Но print работает нормально.

import time
from PySide2.QtWidgets import QApplication, QLabel, QWidget, QPushButton, QHBoxLayout
from PySide2.QtCore import QThread, QObject, Signal
from multiprocessing import Process, Queue


class Worker():
    """Worker running in a Process"""
    def __init__(self):
        self.r0 = 2
        self.N = 10

    def setR0(self, r0):
        self.r0 = r0

    def setN(self, n):
        self.N = n

    def task(self, queue):
        for i in range(self.N):
            res = self.r0 ** i
            queue.put(str(i) + ": " + str(res))
            time.sleep(1)
        queue.put("done")


class Runner(QObject):
    """Runner is running in a qthread"""
    messageLogged = Signal(str)

    def __init__(self, parent=None):
        super().__init__(parent)

    def exec_(self):
        worker = Worker()
        worker.setN(15)
        worker.setR0(3)

        queue = Queue()

        process = Process(target=worker.task, args=(queue,))
        process.start()

        while True:
            msg = queue.get()
            if msg == "done":
                self.messageLogged.emit("received 'done' signal")
                break
            self.messageLogged.emit(msg)

        process.join()


if __name__ == '__main__':
    app = QApplication([])

    widget = QWidget()
    layout = QHBoxLayout()
    widget.setLayout(layout)

    button = QPushButton("Run")
    layout.addWidget(button)

    label = QLabel("initial text")
    layout.addWidget(label)

    thread = QThread()
    r = Runner()
    r.messageLogged.connect(print)
    r.messageLogged.connect(label.setText)
    thread.started.connect(r.exec_)

    def onButtonPushed():
        thread.start()
        button.setEnabled(False)

    button.clicked.connect(onButtonPushed)

    widget.show()
    app.exec_()

Чего мне не хватает, чтобы интерфейс реагировал? У меня такое ощущение, что мой слушатель l oop также должен быть Process, но тогда как мне подать сигнал, который можно подключить к моей метке?

1 Ответ

0 голосов
/ 28 апреля 2020

Я решил свою проблему, удалив QThread и использовав вместо него QTimer. Таймер считывает все сообщения в очереди с регулярным интервалом, а затем позволяет GUI снова реагировать на некоторое время, до следующего тайм-аута.

import time

from PySide2.QtWidgets import QApplication, QLabel, QWidget, QPushButton, QHBoxLayout
from PySide2.QtCore import QTimer
from multiprocessing import Process, Queue


class Worker():
    """Worker running in a Process"""
    def __init__(self):
        self.r0 = 2
        self.N = 10

    def setR0(self, r0):
        self.r0 = r0

    def setN(self, n):
        self.N = n

    def task(self, queue):
        for i in range(self.N):
            res = self.r0 ** i
            queue.put(str(i) + ": " + str(res))
            time.sleep(1)
            print(i)
        queue.put("done")


if __name__ == '__main__':
    app = QApplication([])

    widget = QWidget()
    layout = QHBoxLayout()
    widget.setLayout(layout)

    button = QPushButton("Run")
    layout.addWidget(button)

    label = QLabel("initial text")
    layout.addWidget(label)

    button2 = QPushButton("Kill")
    button2.setEnabled(False)
    layout.addWidget(button2)

    queue = Queue()
    worker = Worker()
    worker.setN(15)
    worker.setR0(3)

    process = Process(target=worker.task, args=(queue, ))

    def read():
        while not queue.empty():
            msg = queue.get()
            if msg == "done":
                label.setText("received 'done' signal")
            else:
                label.setText(msg)

    timer = QTimer()
    timer.timeout.connect(read)

    def onRunButtonPushed():
        print("run clicked")
        process.start()
        timer.start(1000)
        button.setEnabled(False)
        button2.setEnabled(True)

    def onKillButtonPushed():
        process.terminate()
        timer.stop()

    button.clicked.connect(onRunButtonPushed)
    button2.clicked.connect(onKillButtonPushed)

    widget.show()
    app.exec_()
...