Qt5应用调用QMainWindow.close()无法退出,打开QFileDialog后触发该问题
PyQt5调用QFileDialog后进程无法正常退出问题
问题表现
我在QMainWindow的构造函数中执行了多项初始化操作,包含如下退出相关代码:
self.exitAct = QAction("Exit", self, shortcut="Ctrl+Q", triggered=self.close) ... self.fileMenu = QMenu("File", self) self.fileMenu.addAction(self.exitAct)
除此之外我还在构造函数中调用了文件选择对话框,代码如下:
options = QFileDialog.Options() fileName, _ = QFileDialog.getOpenFileName(self, 'QFileDialog.getOpenFileName()', '', 'Images (*.png *.jpeg *.jpg *.bmp *.gif *.yaml)', options=options)
当我按下Ctrl+Q快捷键或者点击File->Exit选项时,出现一致的现象:GUI界面正常消失,无任何报错输出,但进程始终驻留不终止。Linux环境下从shell启动应用后,甚至无法通过Ctrl+C(发送SIGINT信号)杀死进程,只能使用kill命令发送SIGTERM信号才能终止进程。
我的应用没有自定义任何close相关方法,直接调用的是原生QMainWindow.close()方法。
最初我完全没有排查方向,后来验证确认是QFileDialog本身导致的该异常,移除QFileDialog相关代码后应用退出逻辑恢复正常。
最小复现代码
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys, os from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * class MainWindow(QMainWindow): def __init__(self, fileName=None): super().__init__() self.exitAct = QAction("Exit", self, shortcut="Ctrl+Q", triggered=self.close) self.fileMenu = QMenu("File", self) self.fileMenu.addAction(self.exitAct) self.menuBar().addMenu(self.fileMenu) self.openFile() def openFile(self): options = QFileDialog.Options() fileName, _ = QFileDialog.getOpenFileName(self, 'QFileDialog.getOpenFileName()', '', 'Images (*.png *.jpeg *.jpg *.bmp *.gif *.yaml)', options=options) # load some data ... # ... if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
修复后应用组件列表
修复问题后执行print(app.allWidgets())的输出如下:
[<PyQt5.QtWidgets.QMenu object at 0x7fb8e6ae49d0>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6ae4b80>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6ae4c10>, <PyQt5.QtWidgets.QMenu object at 0x7fb8e6ae4ca0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6ae4d30>, <PyQt5.QtWidgets.QPushButton object at 0x7fb8e6ae4160>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6ae4dc0>, <PyQt5.QtWidgets.QLineEdit object at 0x7fb8ee6ea790>, <PyQt5.QtWidgets.QMenu object at 0x7fb8e6ae4790>, <PyQt5.QtWidgets.QLineEdit object at 0x7fb8ee6eae50>, <PyQt5.QtWidgets.QFrame object at 0x7fb8e6ae4e50>, <PyQt5.QtWidgets.QScrollArea object at 0x7fb8ee6ea5e0>, <PyQt5.QtWidgets.QLabel object at 0x7fb8ee6ea670>, <PyQt5.QtWidgets.QLabel object at 0x7fb8e6ae40d0>, <PyQt5.QtWidgets.QToolButton object at 0x7fb8e6ae4ee0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6ae4f70>, <PyQt5.QtWidgets.QLineEdit object at 0x7fb8ee6eac10>, <PyQt5.QtWidgets.QFrame object at 0x7fb8e6af5040>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af50d0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5160>, <PyQt5.QtWidgets.QLineEdit object at 0x7fb8ee6ea9d0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6ae4af0>, <__main__.MainWindow object at 0x7fb8ee6ea160>, <PyQt5.QtWidgets.QLabel object at 0x7fb8e6ae4040>, <PyQt5.QtWidgets.QComboBox object at 0x7fb8ee6ea430>, <PyQt5.QtWidgets.QFrame object at 0x7fb8e6af51f0>, <PyQt5.QtWidgets.QListView object at 0x7fb8e6af5280>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5310>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af53a0>, <__main__.DataArea object at 0x7fb8ee6ea550>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5430>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af54c0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5550>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af55e0>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5670>, <PyQt5.QtWidgets.QMenuBar object at 0x7fb8e6ae4a60>, <PyQt5.QtWidgets.QLabel object at 0x7fb8ee6eadc0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5700>, <PyQt5.QtWidgets.QFrame object at 0x7fb8e6af5790>, <PyQt5.QtWidgets.QListView object at 0x7fb8e6af5820>, <PyQt5.QtWidgets.QComboBox object at 0x7fb8ee6eab80>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af58b0>, <PyQt5.QtWidgets.QListView object at 0x7fb8e6af5940>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af59d0>, <PyQt5.QtWidgets.QLabel object at 0x7fb8ee6eaaf0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5a60>, <PyQt5.QtWidgets.QComboBox object at 0x7fb8ee6ea4c0>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5af0>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5b80>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5c10>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5ca0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5d30>, <PyQt5.QtWidgets.QLabel object at 0x7fb8ee6eaca0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6af5dc0>, <PyQt5.QtWidgets.QLabel object at 0x7fb8ee6ea820>, <PyQt5.QtWidgets.QLabel object at 0x7fb8ee6ea940>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5e50>, <PyQt5.QtWidgets.QScrollBar object at 0x7fb8e6af5ee0>, <PyQt5.QtWidgets.QComboBox object at 0x7fb8ee6ea700>, <PyQt5.QtWidgets.QListView object at 0x7fb8e6af5f70>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6ae41f0>, <PyQt5.QtWidgets.QWidget object at 0x7fb8e6afc040>, <PyQt5.QtWidgets.QMenu object at 0x7fb8e6ae48b0>]
进一步简化的最小复现代码
该版本代码运行后问题表现完全一致:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys, os from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * class MainWindow(QMainWindow): def __init__(self, fileName=None): super().__init__() key = QShortcut(QKeySequence("Ctrl+Q"), self) key.activated.connect(self.close) self.openFile() def openFile(self): fileName, _ = QFileDialog.getOpenFileName(self, 'QFileDialog.getOpenFileName()', '', 'Images (*.png *.jpeg *.jpg *.bmp *.gif *.yaml)') #,options=QFileDialog.DontUseNativeDialog) if __name__ == '__main__': app = QApplication(sys.argv) window = MainWindow() window.show() sys.exit(app.exec_())
解决方案
该问题属于Linux平台下原生文件对话框的已知缺陷,原生对话框关闭后会残留后台线程导致Qt事件循环无法正常退出,可选择两种方案修复:
- 强制使用Qt自带文件对话框,避免调用系统原生对话框,修改代码如下:
fileName, _ = QFileDialog.getOpenFileName(self, 'QFileDialog.getOpenFileName()', '', 'Images (*.png *.jpeg *.jpg *.bmp *.gif *.yaml)', options=QFileDialog.DontUseNativeDialog)
- 重写主窗口的closeEvent方法,确认窗口关闭后主动终止进程:
def closeEvent(self, event): super().closeEvent(event) if event.isAccepted(): sys.exit(0)
内容的提问来源于stack exchange,提问作者Ralf Ulrich
相关产品推荐
相关产品推荐

