Python中关于pyqt5跨进程操作ui的问题
是这样的,我用 pyqt5 写了一个 gui 程序,在主线程中用 multiprocessing 启动新的进程执行一些任务。现在我想把这个子进程的 log 输出到 ui 上,采用了添加 logging.Handler 的方式获取子进程的 log ,然后写入到 ui 中。但是问题来了,由于 qt 的进程安全机制,明明已经获取到 log 了,但是它就是不让我输出到 ui 中,通过 print 的方式是可以显示的。
所以想问下各位大神,我需要怎么做才能让它把 log 写到 ui 中?
代码大概是这样的:
class MyLogHandler(logging.Handler):
def __init__(self, obj):
logging.Handler.__init__(self)
self.Object = obj
def emit(self, record):
if record.levelno<self.level: return
tstr = time.strftime('%Y-%m-%d %H:%M:%S.%U')
self.Object.append("[%s][%s] %s"%(tstr, record.levelname, record.getMessage()))
self.Object.moveCursor(QtGui.QTextCursor.End)
mySW = MainWindow()
handler = MyLogHandler(mySW.loggingBrowser)
logging.getLogger().addHandler(handler)
multiprocessing.Process(pass).start()
其中的 loggingBrowser就是 log 显示组件。由于 logging 是模块级别的,因此主线程中的MyLogHandler 可以捕获到子进程的 log 输出。
补充:
当然,如果用多线程的话是可以的,但是不管是python的多线程还是qt的多线程,都存在无法强制结束子线程的问题。所以只能用多进程了。
Python中关于pyqt5跨进程操作ui的问题
用信号不行吗?
signal = pyqtSignal(str)
signal.connect(log_func) # 主线程
signal.emit(log_content) # 子线程
这个问题我遇到过。在PyQt5里跨进程操作UI确实比较麻烦,因为Qt的UI组件不是线程安全的,更别说跨进程了。
核心思路是使用信号槽机制配合多进程模块的队列。下面是个完整的例子:
import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QTextEdit
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot
from multiprocessing import Process, Queue
import time
# 信号代理类,在主进程中运行
class SignalProxy(QObject):
update_text = pyqtSignal(str)
class WorkerProcess:
def __init__(self, queue):
self.queue = queue
def run(self):
# 模拟耗时操作
for i in range(5):
time.sleep(1)
# 通过队列发送消息给主进程
self.queue.put(f"子进程消息 {i+1}")
self.queue.put("DONE")
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.init_ui()
self.queue = Queue()
self.proxy = SignalProxy()
self.proxy.update_text.connect(self.update_text_edit)
def init_ui(self):
self.setGeometry(300, 300, 400, 300)
self.text_edit = QTextEdit(self)
self.text_edit.setGeometry(50, 50, 300, 150)
self.btn = QPushButton('启动子进程', self)
self.btn.setGeometry(150, 220, 100, 30)
self.btn.clicked.connect(self.start_worker)
def start_worker(self):
worker = WorkerProcess(self.queue)
self.process = Process(target=worker.run)
self.process.start()
# 启动定时器检查队列
self.startTimer(100)
def timerEvent(self, event):
while not self.queue.empty():
msg = self.queue.get()
if msg == "DONE":
self.process.join()
return
# 通过信号槽更新UI
self.proxy.update_text.emit(msg)
@pyqtSlot(str)
def update_text_edit(self, text):
self.text_edit.append(text)
if __name__ == '__main__':
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
关键点:
- 子进程不能直接操作UI组件,必须通过队列传递数据
- 主进程用定时器或线程轮询队列,收到数据后通过信号槽更新UI
- SignalProxy作为桥梁,确保信号在主线程中发出
简单说就是:子进程→队列→主进程→信号槽→UI更新。
1L 正解
根本就不是这个问题,在 multiprocessing 开启的子进程和父进程之间是隔离的,你所说的“由于 logging 是模块级别的,因此主线程中的 MyLogHandler 可以捕获到子进程的 log 输出。”这句话本身就不成立,你用 print 能看到输出是因为这个是子进程直接输出到 stdout 了,根本就不是你父进程捕获到的
你要想解决这个问题,需要通过进程间通讯来传递你的日志信息
试了不行,难道是我姿势不对 ?
修改之后的代码大概是这个样子的:python<br><br>class MyLogHandler(logging.Handler):<br> def __init__(self, obj):<br> logging.Handler.__init__(self)<br> self.Object = obj<br><br><br> def emit(self, record):<br> if record.levelno<self.level: return<br> tstr = time.strftime('%Y-%m-%d %H:%M:%S.%U')<br> self.Object.sin.emit("[%s][%s] %s" %(tstr, record.levelname, record.getMessage()))<br> self.Object.loggingBrowser.moveCursor(QtGui.QTextCursor.End)<br><br>class MainWindow(QMainWindow, Ui_MainWindow):<br> sin = pyqtSignal(str)<br> def __init__(self, parent=None):<br> super(MainWindow, self).__init__(parent)<br> self.setupUi(self)<br> self.sin.connect(self.loggingBrowser.append)<br> handler = MyLogHandler(mySW.loggingBrowser)<br> logging.getLogger().addHandler(handler)<br> ........<br> multiprocessing.Process(pass).start()<br><br><br>mySW = MainWindow()<br><br><br>
手误,打错了。
handler = MyLogHandler(mySW.loggingBrowser) 这一行应该是 handler = MyLogHandler(mySW)
pyqt 的 Signal 只是能跨线程,不能跨进程传输吧
是正解
是的,如你所说。是我理解错了。看来只能用进程间通信了。。
最简单的方法就是用 的方法,而跨进程 Queue 可以使用 multiprocessing.Queue 来实现
不过要注意 multiprocessing 类库中提供的跨进程通讯代理类的使用有一个小问题,就是不能反复创建新的线程来调用这些代理方法,否则会导致他内部创建过多的 socket 连接,最后被操作系统 kill 掉
如果用进程间通信的话,感觉还不如用多进程。想请教下两位大神,如何强制关闭 Qthread 或 threading 生成的线程? 我试过 Qthread 的 terminate() wait() , wait() 之后就会无限等待,根本不会结束。因为我要启动的子线程是不可控的,因此不可能通过修改子线程的源码来自动退出。只能是由主线程结束。
我记得是可以通过 cytpes 调用 cpython 的 c 函数使得在另外一个进程中强行抛出一个 BaseExecption ,具体的办法你可以自己查查
差点大意了 ,果然是进程间通信
试了没效果。我感觉如果用进程间通信的话还不如把 log 写入文件,然后主进程去读取。反正都要保存 log 的。
如果多进程去读写文件容易出更多的奇奇怪怪的坑,具体的你自己衡量吧
对了,我前面说的那个强行抛异常只适用于多线程环境,不能跨进程抛异常
用了进程间通信之后最终还是决定使用写入文件的方式,主进程和子进程程都把 log 写入文件,需要查看的 log 的时候开一个子线程读取然后显示。我觉得这样开销会小一点。
另外想问下大神,如何优化 pyqt5 的内存占用呢 ?具体见这个帖子 https://www.v2ex.com/t/347235
那你最好注意不要在主进程和子进程中同时写文件,否则…
pyqt5 我没仔细研究过,所以帮不上你



