通过日志更新QTextEdit时PyQt5程序崩溃 [英] PyQt5 Program Crashes While Updating QTextEdit via logging

查看:433
本文介绍了通过日志更新QTextEdit时PyQt5程序崩溃的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个大型程序,需要很长时间,需要足够的日志记录.我的前端有一个GUI,其中包含如下定义的自定义日志记录处理程序:

I have a large program that takes a long time that needs ample logging. I have a GUI that is on the front end that includes a custom logging handler as defined below:

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),它可以通过以下方式添加此处理程序:

I have the main gui (QMainWindow) that adds this handler via:

# 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') 

我有控制器功能,可通过以下方式初始化日志记录处理程序:

And I have the controller function that initializes the logging handler via:

# 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执行:

Once the GUI is raised and logging is initialized, I finish the python execution with:

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

GUI的几个插槽连接到按钮信号,这些按钮产生线程以进行实际处理.每个处理线程都有它自己的日志记录调用,该调用似乎可以按预期工作.它们的定义如下:

The GUI has a few slots connected to pushbutton signals that spawns threads to do the actual processing. Each processing thread has it's own logging call which seems to work as intended. They are defined like follows:

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).

When I run my application without a GUI or even with the GUI minimized, it runs fine every time. I can see my log messages in the console (either command prompt or spyder).

如果在不最小化GUI的情况下运行相同的应用程序,我将在GUI中看到用于初始化的日志消息以及线程进程的前几部分,但随后它将挂起,看似随机.没有错误消息,并且正在使用的单个内核的CPU使用率似乎已达到极限.我包括了一个锁,只是为了确保logging不会来自不同的线程,但这也无济于事.

If I run the same application without minimizing the GUI, I will see the log messages in the GUI for initialization and some of the first parts of the threaded process, but then it will hang at seemingly random times. There is no error message and the CPU usage seems to be maxed out for the single core that is being used. I included a lock just to make sure logging wasn't coming in from different threads, but that also didn't help.

我尝试去QPlainTextEditQListWidget,但是每次都遇到相同的问题.

I've tried going to a QPlainTextEdit and a QListWidget but I get the same problem every time.

有人知道这个GUI元素为什么在视图中并记录消息时会导致整个Python解释器挂起吗?

Does anyone have an idea of why this GUI element would cause the entire Python interpreter to hang when in view and messages are logged to it?

推荐答案

采样的QHandler不是线程安全的,因此如果您从另一个线程调用它(因为它是GUI),它将产生问题,一种可能的解决方案是要将数据从辅助线程(def emit(self, record):)通过QMetaObject发送到GUI的线程,必须使用pyqtSlot:

The QHandler that samples is not thread-safe so it will generate problems if you call it from another thread since it is a GUI, a possible solution is to send the data from the secondary thread(def emit(self, record):) to the thread of the GUI through QMetaObject for this you must use 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_())

这篇关于通过日志更新QTextEdit时PyQt5程序崩溃的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆