PySide6中QRunnable随机SegFault或线程未启动问题排查
PySide6多线程随机启动失败/段错误问题分析与修复
问题描述
参照PySide6多线程教程改编代码后,线程有时无法正常启动,还会随机触发Segmentation Fault。可复现问题的最小代码如下:
import sys import time from PySide6.QtCore import QThreadPool, Slot, Signal, QObject, QRunnable from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout, QPushButton, QListWidget class Device: def long_function(self): time.sleep(1) return ['ciao','test'] class Ui_MainWindow(QMainWindow): def __init__(self): super().__init__() self.resize(800, 600) self.setWindowTitle('Test1') layout = QVBoxLayout() self.button_start = QPushButton('Start') layout.addWidget(self.button_start) self.log = QListWidget() layout.addWidget(self.log) centralwidget = QWidget() centralwidget.setLayout(layout) self.setCentralWidget(centralwidget) class WorkerSignals(QObject): result = Signal(object) class Worker(QRunnable): def __init__(self,fn): super().__init__() self.fn = fn self.signals = WorkerSignals() @Slot() def run(self): res = self.fn() self.signals.result.emit(res) class Controller: def __init__(self,ui,dev): self.ui = ui self.dev = dev self.ui.button_start.clicked.connect(self.start) self.threadpool = QThreadPool() self.ui.show() def start(self): worker = Worker(lambda : self.dev.long_function()) worker.signals.result.connect(self.update) self.threadpool.start(worker) self.ui.log.addItem('Started') @Slot() def update(self,l): self.ui.log.addItem('Ende d') self.ui.log.addItems(l) def main(): app = QApplication(sys.argv) mainwindow = Ui_MainWindow() dev = Device() controller = Controller(mainwindow,dev) sys.exit(app.exec()) if __name__ == '__main__': main()
问题分析
核心原因是Worker实例的生命周期管理不当:
- 在
Controller.start()方法中,Worker是局部变量,方法执行完毕后,Python垃圾回收机制可能提前回收该对象。 - 虽然
QThreadPool会处理QRunnable的C++层对象,但Python层的Worker实例被回收后,其内部的WorkerSignals(QObject子类)也会被销毁。若此时后台线程仍在运行,或信号result尚未完成发送/处理,就会导致访问已销毁的对象,进而引发段错误或线程异常终止。
修复方案
确保Worker实例在任务完成前不会被Python垃圾回收,以下两种方法均可解决问题:
方法1:在Controller中维护Worker引用
在Controller中添加列表存储活跃的Worker,任务完成后移除引用:
class Controller: def __init__(self,ui,dev): self.ui = ui self.dev = dev self.ui.button_start.clicked.connect(self.start) self.threadpool = QThreadPool() self.active_workers = [] # 存储活跃Worker实例 self.ui.show() def start(self): worker = Worker(lambda : self.dev.long_function()) worker.signals.result.connect(self.update) # 任务完成后从列表移除引用 worker.signals.result.connect(lambda: self.active_workers.remove(worker)) self.active_workers.append(worker) self.threadpool.start(worker) self.ui.log.addItem('Started') @Slot() def update(self,l): self.ui.log.addItem('Ended') # 修正原代码的空格拼写错误 self.ui.log.addItems(l)
方法2:禁用自动删除,手动管理Worker生命周期
修改Worker类,禁用自动删除,任务完成后手动触发销毁:
class Worker(QRunnable): def __init__(self,fn): super().__init__() self.fn = fn self.signals = WorkerSignals() self.setAutoDelete(False) # 禁用自动删除 @Slot() def run(self): res = self.fn() self.signals.result.emit(res) self.signals.result.disconnect() # 断开所有信号连接 self.deleteLater() # 手动触发对象销毁
内容的提问来源于stack exchange,提问作者Filippo Volpe
相关产品推荐
相关产品推荐

