python multiprocessing - 将子进程日志发送到在父进程中运行的GUI

2023-12-31

我正在编写的一些分析代码之上构建一个接口,用于执行一些 SQL 并处理查询结果。我想向用户公开此分析代码中围绕许多事件的日志记录。因为分析代码运行时间相当长,并且因为我不希望 UI 阻塞,所以到目前为止我已经通过将分析函数放入其自己的线程中来完成此操作。

我现在拥有的简化示例(完整脚本):

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库似乎是可行的方法(无法重新编写分析代码以允许定期轮询,因为大部分时间都花在等待查询返回结果上)。通过使用不阻塞 UI 的方式执行分析代码,可以很容易地获得相同的功能multiprocessing.Pool and 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

但我似乎无法弄清楚如何从子进程检索日志输出并将其传递给父进程以更新日志对话框。我已经阅读了几乎所有关于此问题的问题以及如何处理从多个进程写入单个日志文件的食谱示例,但我无法集中注意力如何使这些想法适应我​​的想法我想在这里做。

Edit

因此,试图弄清楚为什么我看到的行为与我添加的 @eyllanesc 不同:

logger = logging.getLogger()
print(f'In Func: {logger} at {id(logger)}')

and

logger = logging.getLogger()
print(f'In Main: {logger} at {id(logger)}')

to long_task and Mainwindow.start, 分别。当我跑步时main.py I get:

In Main: <RootLogger root (INFO)> at 2716746681984
In Func: <RootLogger root (WARNING)> at 1918342302352

这似乎是中描述的这个问题 https://stackoverflow.com/questions/34724643/python-logging-with-multiprocessing-root-logger-different-in-windows

这个想法使用一个Queue and QueueHandler虽然作为一个解决方案似乎类似于@eyllanesc的原始解决方案


信号不在进程之间传输数据,因此在这种情况下必须使用 Pipe,然后发出信号:

# other imports
import threading
# ...

class LogHandler(logging.Handler):
    def __init__(self):
        super().__init__()
        self.r, self.w = multiprocessing.Pipe()
        self.emitter = LogEmitter()
        threading.Thread(target=self.listen, daemon=True).start()

    def emit(self, record):
        msg = self.format(record)
        self.w.send(msg)

    def listen(self):
        while True:
            try:
                msg = self.r.recv()
                self.emitter.sigLog.emit(msg)
            except EOFError:
                break

# ...
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

python multiprocessing - 将子进程日志发送到在父进程中运行的GUI 的相关文章

随机推荐