Running A Long Python Calculation In A Thread, With Logging To A Qt Window, Crashes After A Short While
Solution 1:
Following the hint by G.M., I have made a version that I think obeys the rules of Qt. I made a ThreadLogger(logging.Handler)
class, which I set to handle all logs in the Worker
thread, and send them to the main thread via slots and signals.
I kept getting the error TypeError: emit() takes 2 positional arguments but 3 were given
when I inherited QtCore.QObject
(and logging.Handler
) in ThreadLogger
, which I suspect was because I was overriding QtCore.Signal.emit() So I put the Signal in a separate MyLog(QObject)
class, and just use an instance of that in ThreadLogger
classMyLog(QtCore.QObject):
# create a new Signal# - have to be a static element# - class has to inherit from QObject to be able to emit signals
signal = QtCore.Signal(str)
# not sure if it's necessary to implement thisdef__init__(self):
super().__init__()
And here is the ThreadLogger(logging.Handler)
class. This just emits all logs via signal
in MyLog
above
# custom logging handler that can run in separate thread, and emit all logs# via signals/slots so they can be used to update the GUI in the main threadclassThreadLogger(logging.Handler):def__init__(self):
super().__init__()
self.log = MyLog()
# logging.Handler.emit() is intended to be implemented by subclassesdefemit(self, record):
msg = self.format(record)
self.log.signal.emit(msg)
The complete code is
import sys
import logging
import numpy as np
from PySide2 import QtWidgets, QtCore
deflongFunction(logger):
logger.info("Start long running function")
i = 0whileTrue:
for j inrange(10000):
t = np.arange(256)
sp = np.fft.fft(np.sin(t))
freq = np.fft.fftfreq(t.shape[-1])
sp = sp + freq
logger.info("%d" % i)
i += 1# since I don't really need an event thread, I subclass QThread, as per# https://woboq.com/blog/qthread-you-were-not-doing-so-wrong.htmlclassWorker(QtCore.QThread):
def__init__(self, parent=None):
super().__init__(parent)
## set up logging# __init__ is run in the thread that creates this thread, not in the# new thread. But logging is thread-safe, so I don't think it matters# create logger for this class
self.logger = logging.getLogger("Worker")
# set up log handler
self.logHandler = ThreadLogger()
self.logHandler.setFormatter(
logging.Formatter('%(asctime)s - %(levelname)s - %(threadName)s - %(message)s'))
self.logger.addHandler(self.logHandler)
# set the logging level
self.logger.setLevel(logging.DEBUG)
defrun(self):
longFunction(self.logger)
classMyLog(QtCore.QObject):
# create a new Signal# - have to be a static element# - class has to inherit from QObject to be able to emit signals
signal = QtCore.Signal(str)
# not sure if it's necessary to implement thisdef__init__(self):
super().__init__()
# custom logging handler that can run in separate thread, and emit all logs# via signals/slots so they can be used to update the GUI in the main threadclassThreadLogger(logging.Handler):
def__init__(self):
super().__init__()
self.log = MyLog()
# logging.Handler.emit() is intended to be implemented by subclassesdefemit(self, record):
msg = self.format(record)
self.log.signal.emit(msg)
classMyWidget(QtWidgets.QDialog):
def__init__(self, parent=None):
super().__init__(parent)
# read-only text box
self.logTextBox = QtWidgets.QPlainTextEdit(self)
self.logTextBox.setReadOnly(True)
self.resize(400, 500)
# start button
self.startButton = QtWidgets.QPushButton(self)
self.startButton.setText('Start')
# connect start button
self.startButton.clicked.connect(self.start)
# set up layout
layout = QtWidgets.QVBoxLayout()
layout.addWidget(self.logTextBox)
layout.addWidget(self.startButton)
self.setLayout(layout)
defstart(self):
self.thread = Worker()
self.thread.finished.connect(self.threadFinished)
self.thread.start()
# we don't want to enable user to start another thread while this one# is running so we disable the start button.
self.startButton.setEnabled(False)
# connect logger
self.thread.logHandler.log.signal.connect(self.write_log)
defthreadFinished(self):
self.startButton.setEnabled(True)
# define a new Slot, that receives a string @QtCore.Slot(str)defwrite_log(self, log_text):
self.logTextBox.appendPlainText(log_text)
self.logTextBox.centerCursor() # scroll to the bottom
app = QtWidgets.QApplication(sys.argv)
w = MyWidget()
w.show()
app.exec_()
I am not quite sure why yet, but I get all logs from longFunction
in the terminal as well as in the GUI window (but with different formats). I am also not 100% if this is actually thread-safe, and obeys all Qt threading rules, but at least it doesn't crash in the same way as before.
I will leave this answer up for a couple of days, and accept it then if I don't get any better answers, or it turns out my solution is wrong!
Post a Comment for "Running A Long Python Calculation In A Thread, With Logging To A Qt Window, Crashes After A Short While"