通过日志更新QTextEdit时PyQt5程序崩溃 [英] PyQt5 Program Crashes While Updating QTextEdit via logging
问题描述
我有一个大型程序,需要很长时间,需要足够的日志记录.我的前端有一个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.
我尝试去QPlainTextEdit
和QListWidget
,但是每次都遇到相同的问题.
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屋!