Boilerplating ведения журнала в QTextEdit из нескольких QThreads без блокировки - PullRequest
0 голосов
/ 10 июля 2019

Cheerio, Qt новичок здесь готов учиться ...

В настоящее время я пытаюсь создать приложение Qt, которое печатает logging.Record объекты из нескольких QThread экземпляров. Для этого я разработал минимальный рабочий пример, включающий само приложение с QTextEdit и QPushButton, настраиваемый регистратор и произвольный длительный процесс, который должен выполняться из отдельного потока.

Код на самом деле работает! Причина, по которой я публикую это здесь, заключается в том, что я хотел бы услышать ваше мнение о том, как улучшить это. Также я надеюсь получить от вас некоторую информацию, чтобы написать для этого стабильный работающий маленький Boilerplate. Я вижу людей, имеющих эту проблему довольно часто.

Так вот код ...

app.py

Устанавливает базовый QApplication и инициализирует QThread, выполняя LongRunningProcess.run().

import sys

from PySide2 import QtCore, QtGui, QtWidgets

from logger import Logger
from lrp import LongRunningProcess


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.log = Logger(self._log_to_qtextedit)
        self.create_gui()

        self.thread = QtCore.QThread()
        self.worker = LongRunningProcess(self._log_to_qtextedit)

        self.thread.started.connect(self.worker.run)
        self.worker.moveToThread(self.thread)
        self.button.clicked.connect(self.run_process)


    def run_process(self):
        self.thread.start()


    def create_gui(self):
        self.centralwidget = QtWidgets.QWidget(self)
        layout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.textedit = QtWidgets.QTextEdit(self)
        # change font of output log to monospaced
        font = QtGui.QFont()
        font.setFamily("Courier New")
        self.textedit.setFont(font)
        self.button = QtWidgets.QPushButton("push me")
        layout.addWidget(self.textedit)
        layout.addWidget(self.button)
        self.setLayout(layout)
        self.setCentralWidget(self.centralwidget)
        self.resize(700, 100)
        self.log.info("Gui initialised...")


    def on_application_shutdown(self):
        self.thread.quit()


    @QtCore.Slot(str)
    def _log_to_qtextedit(self, msg):
        self.textedit.append(msg)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    window = MainWindow()
    app.aboutToQuit.connect(window.on_application_shutdown)
    window.show()
    sys.exit(app.exec_())

logger.py

Здесь я немного запутался, как мне кажется. Для каждого вызывающего пользователя, который создает экземпляр этого регистратора, Queue будет создано и передано QueueHandler. QueueListener подключается к пользовательскому обработчику QtHandler. Этот обработчик создает экземпляр QObject, чтобы иметь возможность излучать сигналы каждый раз, когда вызывается метод обработчика emit(). Этот сигнал будет подключен к переданному log_func из Logger.

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

import logging
import sys
from logging.handlers import QueueHandler, QueueListener
from queue import Queue

from PySide2 import QtCore


class SignalEmitter(QtCore.QObject):
    request_textedit_update = QtCore.Signal(str)
    def __init__(self):
        super(SignalEmitter, self).__init__()


class QtHandler(logging.Handler):
    def __init__(self):
        super(QtHandler, self).__init__()
        self.signal_emitter = SignalEmitter()

    def emit(self, record):
        record = self.format(record)
        self.signal_emitter.request_textedit_update.emit(record)


class Logger(logging.Logger):
    def __init__(self, log_func, level=logging.DEBUG, name="root"):
        super(Logger, self).__init__(name)
        queue = Queue()
        self.setLevel(level)
        self.handlers_ = {
            "queue_handler": QueueHandler(queue),
            "console_handler": logging.StreamHandler()
        }
        formatter = logging.Formatter("%(asctime)s %(name)-12s %(levelname)-8s %(message)s")
        for handler in self.handlers_.values():
            print(handler)
            handler.setFormatter(formatter)
            self.addHandler(handler)

        qthandler = QtHandler()
        qthandler.signal_emitter.request_textedit_update.connect(log_func)
        qthandler.setFormatter(formatter)
        self.listener = QueueListener(
            queue, qthandler
        )
        self.listener.start()

    def stop_listening(self):
        self.listener.stop()

lrp.py

Ну, это может быть что угодно, что требует времени.

from logger import Logger
from PySide2.QtCore import QObject

class LongRunningProcess(QObject):
    def __init__(self, guilog_func):
        super(LongRunningProcess, self).__init__()
        self.log = Logger(guilog_func)

    def run(self):
        for i in range(1000):
            self.log.info(i)
...