簡體   English   中英

通過日志更新QTextEdit時PyQt5程序崩潰

[英]PyQt5 Program Crashes While Updating QTextEdit via logging

我有一個大型程序,需要很長時間,需要足夠的日志記錄。 我在前端有一個GUI,其中包括如下定義的自定義日志記錄處理程序:

class QHandler(logging.Handler, QTextEdit):
    def __init__(self, parent=None):
        QTextEdit.__init__(self, parent)
        logging.Handler.__init__(self)

        self.setLineWrapMode(QTextEdit.NoWrap)
        self.setReadOnly(True)

        self.emit_lock = Lock()

    def emit(self, record):
        with self.emit_lock:
            self.append(self.format(record))
            self.autoScroll()

    def format(self, record):
        if (record.levelno <= logging.INFO):
            bgcolor = WHITE
            fgcolor = BLACK
        if (record.levelno <= logging.WARNING):
            bgcolor = YELLOW
            fgcolor = BLACK
        if (record.levelno <= logging.ERROR):
            bgcolor = ORANGE
            fgcolor = BLACK
        if (record.levelno <= logging.CRITICAL):
            bgcolor = RED
            fgcolor = BLACK
        else:
            bgcolor = BLACK
            fgcolor = WHITE

        self.setTextBackgroundColor(bgcolor)
        self.setTextColor(fgcolor)
        self.setFont(DEFAULT_FONT)
        record = logging.Handler.format(self, record)
        return record

    def autoScroll(self):
        self.verticalScrollBar().setSliderPosition(self.verticalScrollBar().maximum())

我有主要的GUI(QMainWindow)通過以下方式添加此處理程序:

# inside __init__ of main GUI (QMainWindow):
self.status_handler = QHandler()
# Main gui is divided into tabs and the status handler box is added to the second tab
main_tabs.addTab(self.status_handler, 'Status') 

我有控制器功能,可通過以下方式初始化日志記錄處理程序:

# inside controller initializing function
gui = gui_class() # this is the main gui that initializes the handler among other things
logger = logging.getLogger()
gui.status_handler.setFormatter(file_formatter) # defined elsewhere
logger.addHandler(gui.status_handler)

提起GUI並初始化日志記錄后,我將使用以下命令完成python執行:

app = QApplication.instance()
if (app is None):
    app = QApplication([])
    app.setStyle('Fusion')
app.exec_()

GUI具有一些與按鈕信號相連的插槽,這些信號產生線程以進行實際處理。 每個處理線程都有它自己的日志記錄調用,該調用似乎可以按預期工作。 它們的定義如下:

class Subprocess_Thread(Thread):
    def __init__(self, <args>):
        Thread.__init__(self)
        self.logger = logging.getLogger(self.__class__.__name__)
        self.logger.info('Subprocess Thread Created')

    def run(self):
        # does a bunch of stuff
        self.logger.info('Running stuff')
        # iterates over other objects and calls on them to do stuff
        # where they also have a logger attached and called just like above

當我在沒有GUI或什至最小化GUI的情況下運行應用程序時,每次運行都很好。 我可以在控制台中看到我的日志消息(命令提示符或spyder )。

如果我在不最小化GUI的情況下運行相同的應用程序,我將在GUI中看到用於初始化的日志消息以及線程進程的前幾部分,但隨后掛起的時間似乎是隨機的。 沒有錯誤消息,正在使用的單個內核的CPU使用率似乎已達到極限。 我包括了一個鎖,只是為了確保logging不是來自不同的線程,但這也無濟於事。

我嘗試去QPlainTextEditQListWidget但每次都遇到相同的問題。

有誰知道為什么這個GUI元素會導致在視圖中和消息記錄到日志時掛起整個Python解釋器?

采樣的QHandler不是線程安全的,因此如果您從另一個線程(因為它是GUI)調用它,則會產生問題,一種可能的解決方案是將輔助線程中的數據發送( def emit(self, record):到為此,必須通過QMetaObject通過QMetaObject進行GUI線程pyqtSlot

class QHandler(logging.Handler, QtWidgets.QTextEdit):
    def __init__(self, parent=None):
        QtWidgets.QTextEdit.__init__(self, parent)
        logging.Handler.__init__(self)

        self.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
        self.setReadOnly(True)

        self.emit_lock = threading.Lock()

    def emit(self, record):
        with self.emit_lock:
            QtCore.QMetaObject.invokeMethod(self, 
                "append",  
                QtCore.Qt.QueuedConnection,
                QtCore.Q_ARG(str, self.format(record)))
            QtCore.QMetaObject.invokeMethod(self, 
                "autoScroll",
                QtCore.Qt.QueuedConnection)

    def format(self, record):
        if record.levelno == logging.INFO:
            bgcolor = WHITE
            fgcolor = BLACK
        elif record.levelno == logging.WARNING:
            bgcolor = YELLOW
            fgcolor = BLACK
        elif record.levelno == logging.ERROR:
            bgcolor = ORANGE
            fgcolor = BLACK
        elif record.levelno == logging.CRITICAL:
            bgcolor = RED
            fgcolor = BLACK
        else:
            bgcolor = BLACK
            fgcolor = WHITE

        self.setTextBackgroundColor(bgcolor)
        self.setTextColor(fgcolor)
        self.setFont(DEFAULT_FONT)
        record = logging.Handler.format(self, record)
        return record

    @QtCore.pyqtSlot()
    def autoScroll(self):
        self.verticalScrollBar().setSliderPosition(self.verticalScrollBar().maximum())

例:

import random
import logging
import threading
from PyQt5 import QtCore, QtGui, QtWidgets

WHITE, BLACK, YELLOW, ORANGE, RED = QtGui.QColor("white"), QtGui.QColor("black"), QtGui.QColor("yellow"), QtGui.QColor("orange"), QtGui.QColor("red")
DEFAULT_FONT = QtGui.QFont()

class QHandler(logging.Handler, QtWidgets.QTextEdit):
    def __init__(self, parent=None):
        QtWidgets.QTextEdit.__init__(self, parent)
        logging.Handler.__init__(self)

        self.setLineWrapMode(QtWidgets.QTextEdit.NoWrap)
        self.setReadOnly(True)

        self.emit_lock = threading.Lock()

    def emit(self, record):
        with self.emit_lock:
            QtCore.QMetaObject.invokeMethod(self, 
                "append",  
                QtCore.Qt.QueuedConnection,
                QtCore.Q_ARG(str, self.format(record)))
            QtCore.QMetaObject.invokeMethod(self, 
                "autoScroll",
                QtCore.Qt.QueuedConnection)

    def format(self, record):
        if record.levelno == logging.INFO:
            bgcolor = WHITE
            fgcolor = BLACK
        elif record.levelno == logging.WARNING:
            bgcolor = YELLOW
            fgcolor = BLACK
        elif record.levelno == logging.ERROR:
            bgcolor = ORANGE
            fgcolor = BLACK
        elif record.levelno == logging.CRITICAL:
            bgcolor = RED
            fgcolor = BLACK
        else:
            bgcolor = BLACK
            fgcolor = WHITE

        self.setTextBackgroundColor(bgcolor)
        self.setTextColor(fgcolor)
        self.setFont(DEFAULT_FONT)
        record = logging.Handler.format(self, record)
        return record

    @QtCore.pyqtSlot()
    def autoScroll(self):
        self.verticalScrollBar().setSliderPosition(self.verticalScrollBar().maximum())


class MainWindow(QtWidgets.QMainWindow):
    def __init__(self, parent=None):
        super(MainWindow, self).__init__(parent)
        self.status_handler = QHandler()
        self.setCentralWidget(self.status_handler)

        logging.getLogger().addHandler(self.status_handler)
        self.status_handler.setFormatter(logging.Formatter('%(asctime)s - %(levelname)s - %(message)s'))
        logging.getLogger().setLevel(logging.DEBUG)
        timer = QtCore.QTimer(self, interval=1000, timeout=self.on_timeout)
        timer.start()

    def on_timeout(self):
        logging.info('From Gui Thread {}'.format(QtCore.QDateTime.currentDateTime().toString()))


class Subprocess_Thread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.logger = logging.getLogger(self.__class__.__name__)
        self.logger.info('Subprocess Thread Created')

    def run(self):
        while True:
            t = random.choice(["info", "warning", "error", "critical"])
            msg = "Type: {}, thread: {}".format(t, threading.currentThread())
            getattr(self.logger, t)(msg)
            QtCore.QThread.sleep(1)

if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication.instance()
    if app is None:
        app = QtWidgets.QApplication(sys.argv)
        app.setStyle('Fusion')
    w = MainWindow()
    w.show()
    th = Subprocess_Thread()
    th.daemon = True
    th.start()
    sys.exit(app.exec_())

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM