У меня есть большой модуль из отдельного проекта, который я хотел интегрировать в графический интерфейс. Модуль выполняет некоторые вычисления, которые занимают пару минут, и я хочу, чтобы графический интерфейс оставался отзывчивым в течение этого времени, и желательно иметь возможность отменить процесс в любое время.
Лучшим решением, вероятно, было бы переписать модуль с использованием сигналов и многопоточности, но я хотел попробовать сделать это без этого для начала. Итак, моя идея состояла в том, чтобы запустить myLongFunction
в отдельном потоке.
В графическом интерфейсе я сделал текстовое поле (QPlainTextEdit
), в котором я хочу отображать сообщения с помощью средств ведения журнала Python. У меня тоже есть кнопка "Старт".
Программа, кажется, некоторое время работает по назначению, но обычно происходит сбой в течение 10 секунд. Иногда вылетает сразу, иногда чуть дольше. И я не получаю никаких исключений или других ошибок, я просто возвращаюсь в командную строку терминала. Минимальный пример ниже.
import sys
import time
import logging
from PySide2 import QtWidgets, QtCore
import numpy as np
def longFunction():
logging.info("Start long running function")
i = 0
while True:
for j in range(10000):
t = np.arange(256)
sp = np.fft.fft(np.sin(t))
freq = np.fft.fftfreq(t.shape[-1])
sp = sp + freq
logging.info("%d" % i)
i += 1
# I added a sleep here, but it doesn't seem to help
time.sleep(0.001)
# 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.html
class Worker(QtCore.QThread):
def __init__(self, parent=None):
super().__init__(parent)
def run(self):
longFunction()
# custom logging handler
class QTextEditLogger(logging.Handler):
def __init__(self, parent=None):
super().__init__()
self.widget = QtWidgets.QPlainTextEdit(parent)
self.widget.setReadOnly(True)
def emit(self, record):
msg = self.format(record)
self.widget.appendPlainText(msg)
self.widget.centerCursor() # scroll to the bottom
class MyWidget(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
logTextBox = QTextEditLogger(self)
# format what is printed to text box
logTextBox.setFormatter(
logging.Formatter('%(asctime)s - %(levelname)s - %(threadName)s - %(message)s'))
logging.getLogger().addHandler(logTextBox)
# set the logging level
logging.getLogger().setLevel(logging.DEBUG)
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(logTextBox.widget)
layout.addWidget(self.startButton)
self.setLayout(layout)
def start(self):
logging.info('Start button pressed')
self.thread = Worker()
# regardless of whether the thread finishes or the user terminates it
# we want to show the notification to the user that it is done
# and regardless of whether it was terminated or finished by itself
# the finished signal will go off. So we don't need to catch the
# terminated one specifically, but we could if we wanted.
self.thread.finished.connect(self.threadFinished) # new-style signal
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)
def threadFinished(self):
logging.info('Thread finished!')
self.startButton.setEnabled(True)
app = QtWidgets.QApplication(sys.argv)
w = MyWidget()
w.show()
app.exec_()
Самое странное, что если я уберу текстовое поле (закомментируйте строки 51-56 и строку 72), программа запустится просто отлично (я остановил ее вручную через 5 минут).
Любая идея, что может вызвать это?
QTextEditLogger
, который находится в основном потоке графического интерфейса, непосредственно из вашего вторичного потока (то естьWorker
). Это не поддерживается. Ваша первоначальная идея использования сигналов/слотов с соединениями в очереди - это правильный путь. - person G.M.   schedule 24.09.2018