Я строю интерфейс поверх написанного мной кода анализа, который выполняет SQL и обрабатывает результаты запроса.В этом аналитическом коде есть записи о ряде событий, которые я хотел бы представить пользователю.Поскольку код анализа довольно длительный и потому что я не хочу, чтобы пользовательский интерфейс блокировался, до сих пор я делал это, помещая функцию анализа в свой собственный поток.
Упрощенный пример того, что у меня сейчас (полный скрипт):
import sys
import time
import logging
from PySide2 import QtCore, QtWidgets
def long_task():
logging.info('Starting long task')
time.sleep(3) # this would be replaced with a real task
logging.info('Long task complete')
class LogEmitter(QtCore.QObject):
sigLog = QtCore.Signal(str)
class LogHandler(logging.Handler):
def __init__(self):
super().__init__()
self.emitter = LogEmitter()
def emit(self, record):
msg = self.format(record)
self.emitter.sigLog.emit(msg)
class LogDialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super().__init__(parent)
log_txt = QtWidgets.QPlainTextEdit(self)
log_txt.setReadOnly(True)
layout = QtWidgets.QHBoxLayout(self)
layout.addWidget(log_txt)
self.setWindowTitle('Event Log')
handler = LogHandler()
handler.emitter.sigLog.connect(log_txt.appendPlainText)
logger = logging.getLogger()
logger.addHandler(handler)
logger.setLevel(logging.INFO)
class Worker(QtCore.QThread):
results = QtCore.Signal(object)
def __init__(self, func, *args, **kwargs):
super().__init__()
self.func = func
self.args = args
self.kwargs = kwargs
def run(self):
results = self.func(*self.args, **self.kwargs)
self.results.emit(results)
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
widget = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(widget)
start_btn = QtWidgets.QPushButton('Start')
start_btn.clicked.connect(self.start)
layout.addWidget(start_btn)
self.setCentralWidget(widget)
self.log_dialog = LogDialog()
self.worker = None
def start(self):
if not self.worker:
self.log_dialog.show()
logging.info('Run Starting')
self.worker = Worker(long_task)
self.worker.results.connect(self.handle_result)
self.worker.start()
def handle_result(self, result=None):
logging.info('Result received')
self.worker = None
if __name__ == '__main__':
app = QtWidgets.QApplication()
win = MainWindow()
win.show()
sys.exit(app.exec_())
Это прекрасно работает, за исключением того, что я должен иметь возможность позволить пользователю остановить выполнение кода анализа,Все, что я прочитал, указывает на то, что нет способа красиво прервать потоки, поэтому использование библиотеки multiprocessing
кажется правильным (нет способа переписать код анализа для периодического опроса, так как большинствовремени тратится только на ожидание запросов, чтобы вернуть результаты).Достаточно просто получить те же функциональные возможности с точки зрения выполнения кода анализа способом, который не блокирует пользовательский интерфейс с помощью multiprocessing.Pool
и apply_async
.
Например, заменив MainWindow
сверху на:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self):
super().__init__()
widget = QtWidgets.QWidget()
layout = QtWidgets.QHBoxLayout(widget)
start_btn = QtWidgets.QPushButton('Start')
start_btn.clicked.connect(self.start)
layout.addWidget(start_btn)
self.setCentralWidget(widget)
self.log_dialog = LogDialog()
self.pool = multiprocessing.Pool()
self.running = False
def start(self):
if not self.running:
self.log_dialog.show()
logging.info('Run Starting')
self.pool.apply_async(long_task, callback=self.handle_result)
def handle_result(self, result=None):
logging.info('Result received')
self.running = False
Но я не могу понять, как мне поступить, извлекая выходные данные регистрации из дочернего процесса и передавая ихродитель, чтобы обновить диалог журнала.Я прочитал почти каждый вопрос SO по этому вопросу, а также примеры поваренной книги о том, как обрабатывать запись в один файл журнала из нескольких процессов, но я не могу понять, как адаптировать эти идеи к тому, что я делаю ».Я пытаюсь сделать здесь.
Редактировать
Итак, пытаясь выяснить, что может происходить, почему я вижу поведение, отличное от @eyllanesc, я добавил:
logger = logging.getLogger()
print(f'In Func: {logger} at {id(logger)}')
и
logger = logging.getLogger()
print(f'In Main: {logger} at {id(logger)}')
до long_task
и Mainwindow.start
соответственно.Когда я запускаю main.py
, я получаю:
In Main: <RootLogger root (INFO)> at 2716746681984
In Func: <RootLogger root (WARNING)> at 1918342302352
, что похоже на то, что было описано в этом SO вопросе
Эта идея использования Queue
иQueueHandler
хотя решение похоже на оригинальное решение @ eyllanesc