如何从多进程任务中获取日志并在GUI界面展示?
问题分析与解决方案
核心问题在于多进程的日志无法直接通过父进程的Qt信号传递到GUI:每个子进程拥有独立的Python解释器和logging系统,父进程添加的QtHandler不会被子进程继承。子进程中触发的emit属于子进程内的对象,其信号无法跨进程传递到主进程的GUI线程,因此update_log_gui槽函数无法收到信号。
以下是修正后的完整代码:
1. gui.py 修改
from PyQt5 import QtCore, QtWidgets import logging from log_test import main from multiprocessing import Queue import threading Signal = QtCore.pyqtSignal Slot = QtCore.pyqtSlot class QtHandler(logging.Handler): def __init__(self, slotfunc, *args, **kwargs): super().__init__(*args, **kwargs) self.signal = Signal(str, logging.LogRecord) self.signal.connect(slotfunc) def emit(self, record): s = self.format(record) self.signal.emit(s, record) class Worker(QtCore.QObject): finished = Signal() def __init__(self, log_queue): super().__init__() self.log_queue = log_queue @Slot() def start(self): main(self.log_queue) self.finished.emit() class LogQueueListener(QtCore.QObject): log_received = Signal(logging.LogRecord) def __init__(self, log_queue, handler): super().__init__() self.log_queue = log_queue self.handler = handler self.running = True self.log_received.connect(self.handler.emit) # 启动后台监听线程 self.listener_thread = threading.Thread(target=self.listen, daemon=True) self.listener_thread.start() def listen(self): while self.running: try: record = self.log_queue.get(timeout=1) if record is None: # 结束标记 break self.log_received.emit(record) except: pass class Ui_Dialog(QtCore.QObject): def __init__(self): super().__init__() self.log_queue = Queue() def setupUi(self, Dialog): Dialog.setObjectName("Dialog") Dialog.setEnabled(True) Dialog.resize(530, 440) self.verticalLayout = QtWidgets.QVBoxLayout(Dialog) self.verticalLayout.setObjectName("verticalLayout") self.button = QtWidgets.QPushButton(Dialog) self.button.setText("start working") self.verticalLayout.addWidget(self.button) self.logWidget = QtWidgets.QPlainTextEdit(Dialog) self.logWidget.setReadOnly(True) self.verticalLayout.addWidget(self.logWidget) # 初始化日志处理器与队列监听 self.handler = QtHandler(self.update_log_gui) root_logger = logging.getLogger('log') root_logger.addHandler(self.handler) root_logger.setLevel(logging.ERROR) # 设置日志格式 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') self.handler.setFormatter(formatter) # 启动队列监听 self.log_listener = LogQueueListener(self.log_queue, self.handler) self.button.clicked.connect(self.start_work) @Slot(str, logging.LogRecord) def update_log_gui(self, status, record): self.logWidget.appendPlainText(status) def config_thread(self): self.worker_thread = QtCore.QThread() self.worker_thread.setObjectName('WorkerThread') self.worker = Worker(self.log_queue) self.worker.moveToThread(self.worker_thread) self.worker_thread.started.connect(self.worker.start) self.worker.finished.connect(self.worker_thread.quit) self.worker.finished.connect(self.worker.deleteLater) self.worker_thread.finished.connect(self.worker_thread.deleteLater) self.worker_thread.finished.connect(lambda: self.button.setEnabled(True)) # 线程结束时停止日志监听 self.worker_thread.finished.connect(self.stop_log_listener) def stop_log_listener(self): self.log_listener.running = False self.log_queue.put(None) # 发送结束信号 def start_work(self): self.config_thread() self.worker_thread.start() self.button.setEnabled(False) if __name__ == "__main__": import sys QtCore.QThread.currentThread().setObjectName('MainThread') app = QtWidgets.QApplication(sys.argv) Dialog = QtWidgets.QDialog() ui = Ui_Dialog() ui.setupUi(Dialog) Dialog.show() sys.exit(app.exec_())
2. log_test.py 修改
import logging import time from multiprocessing import Pool from logging.handlers import QueueHandler def configure_logging(log_queue): # 为每个子进程初始化日志处理器 root_logger = logging.getLogger('log') root_logger.setLevel(logging.ERROR) root_logger.addHandler(QueueHandler(log_queue)) def f(name): logger = logging.getLogger('log.' + name) logger.error('hello there 1') time.sleep(0.5) logger.error('hello there 2') time.sleep(0.5) logger.error('hello there 3') time.sleep(0.5) def main(log_queue): # 初始化进程池时配置子进程日志 with Pool(5, initializer=configure_logging, initargs=(log_queue,)) as p: p.map(f, ['aaa', 'bbb', 'ccc'])
关键修改说明
- 跨进程日志传输:使用
multiprocessing.Queue作为中间载体,子进程通过QueueHandler将日志记录放入队列,父进程通过LogQueueListener线程监听队列。 - Qt信号安全调度:
LogQueueListener将队列中获取的日志通过Qt信号转发到QtHandler,确保日志更新操作在主GUI线程执行(Qt信号跨线程会自动调度到接收对象的线程)。 - 子进程日志初始化:通过
Pool的initializer参数,在每个子进程启动时统一配置日志处理器,确保所有子进程的日志都能进入队列。
内容的提问来源于stack exchange,提问作者나상원
相关产品推荐
相关产品推荐

