PyQt技术问题:QThread执行期间QDialog无法按需显示
问题描述
我希望在QThread执行PyVisa仪器连接操作时显示QDialog,但当前程序会先完成QThread任务,再显示QDialog。同时我想理解QThread的运行逻辑。以下是我的原始代码及更新后的代码:
主程序代码
from PyQt5 import QtWidgets from baa_connexion import BAA myApp = QtWidgets.QApplication(sys.argv) myApp.setStyle('Fusion') #application window = BAA() window.show() #Close programm myApp.quit() myApp.exec_()
原始baa_connexion.py代码
import pyvisa import logging from VirtualInstrument import VirtualInstrument from PyQt5 import QtCore, QtWidgets, QtGui, Qt class BAA(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.information_settings() def open_connexion(self): self.qdiag = QtWidgets.QDialog(self.centralwidget) self.hbox_dial = QtWidgets.QLabel("Connection....") self.hbox_con = QtWidgets.QHBoxLayout(self.qdiag) self.hbox_con.addWidget(self.hbox_dial) self.Worker = QWorkerthread() self.Worker.finished.connect(self.Worker.deleteLater) self.Worker.finished.connect(self.qdiag.close) self.Worker.finished.connect(self.qdiag.deleteLater) self.Worker.start() self.status_thread = self.Worker.connexion_asa() if self.status_thread == True : print("Connect OK") return True else: print("No connected") return False self.qdiag.setLayout(self.hbox_con) self.qdiag.exec() def information_settings(self): self.centralwidget = QtWidgets.QWidget(self) self.enter = QtWidgets.QPushButton(self.centralwidget) self.enter.clicked.connect(self.suite) self.hbox_infos = QtWidgets.QHBoxLayout(self.centralwidget) self.hbox_infos .addWidget(self.enter) self.setLayout(self.hbox_infos) self.setCentralWidget(self.centralwidget) class QWorkerthread(Qt.QThread): #worker_finished = QtCore.pyqtSignal(bool) #finished = QtCore.pyqtSignal() def connexion_asa(self): self.rm = pyvisa.ResourceManager() self.fichier = open("ipasa.txt", 'r') self.ip = self.fichier.read() try: self.inst = self.rm.open_resource("TCPIP0::" + self.ip + "::inst0::INSTR", timeout=25000) logging.info("Connection Ok") return True except: logging.error('No connection with Spectral Analyzer') #self.worker_finished.emit(True) return False self.finished.emit()
更新后代码
import pyvisa import logging import time from PyQt5 import QtCore, QtWidgets, QtGui, Qt class BAA(QtWidgets.QMainWindow): def __init__(self): super().__init__() self.information_settings() def open_connexion(self): self.qdiag = QtWidgets.QDialog() self.hbox_con = QtWidgets.QHBoxLayout() self.hbox_dial = QtWidgets.QLabel("Connection....") self.hbox_con.addWidget(self.hbox_dial) self.qdiag.setLayout(self.hbox_con) Worker = myThread() Worker.status_connect.connect(self.receive_status) #SIGNAL Worker.finished.connect(Worker.deleteLater) Worker.finished.connect(self.qdiag.close) Worker.finished.connect(self.qdiag.deleteLater) Worker.start() self.qdiag.exec() def receive_status(self, etat): print(etat) if etat == False: print("Connection failed") return False else: print("OK Conenct") return True def new_sheet(self): self.centralwidget = QtWidgets.QWidget(self) self.bjr = QtWidgets.QLabel(self.centralwidget) self.open_connexion() #IF status_connect IS TRUE : #self.bjr.setText("Ok Conenct") #else: #self.bjr.setText("No connection") self.gh = QtWidgets.QHBoxLayout(self.centralwidget) self.gh.addWidget(self.bjr) self.setLayout(self.gh) self.setCentralWidget(self.centralwidget) def information_settings(self): self.centralwidget = QtWidgets.QWidget(self) self.enter = QtWidgets.QPushButton(self.centralwidget) self.enter.clicked.connect(self.new_sheet) self.hbox_infos = QtWidgets.QHBoxLayout(self.centralwidget) self.hbox_infos.addWidget(self.enter) self.setLayout(self.hbox_infos) self.setCentralWidget(self.centralwidget) class myThread(Qt.QThread): status_connect = Qt.pyqtSignal(bool) def run(self): # time consuming actions self.rm = pyvisa.ResourceManager() self.fichier = open("ipasa.txt", 'r') self.ip = self.fichier.read() try: self.inst = self.rm.open_resource("TCPIP0::" + self.ip + "::inst0::INSTR", timeout=25000) self.status_connect.emit(True) except: self.status_connect.emit(False) print("oh") time.sleep(2)
问题分析与解决
一、QThread核心运行逻辑
- QThread是Qt封装的线程类,调用
start()方法后,会在独立新线程中执行run()函数内的代码,主线程不受阻塞,会继续执行后续逻辑。 - 线程间必须通过信号-槽机制传递数据或状态,直接调用线程类的普通方法会在主线程执行,而非新线程,这是常见错误。
QDialog.exec()是模态对话框,会阻塞当前线程的事件循环,直到对话框被关闭。
二、原始代码的核心问题
- 错误调用线程方法:
self.status_thread = self.Worker.connexion_asa()直接调用线程类方法,导致PyVisa连接操作在主线程执行,主线程被阻塞,弹窗代码self.qdiag.exec()根本没机会运行,直到连接完成才会执行弹窗。 - 未重写
run()方法:QThread默认run()是空实现,自定义的connexion_asa()不会自动在新线程执行,必须把耗时逻辑放到run()中。
三、更新后代码的优化与修复
更新后的代码已经修正了核心问题,可进一步完善细节:
1. 确保弹窗正常显示
更新后代码中Worker.start()后调用self.qdiag.exec()的逻辑是正确的:模态弹窗阻塞主线程,但线程已在后台启动,弹窗会正常显示,直到线程完成后触发finished信号关闭弹窗。
2. 完善UI状态更新
receive_status槽函数无需返回值,直接在里面更新主窗口标签即可:
def receive_status(self, etat): if etat: self.bjr.setText("连接成功") print("OK Connect") else: self.bjr.setText("连接失败") print("Connection failed")
3. 线程资源与细节优化
- 线程对象
Worker作为局部变量可能被提前回收,建议改为类成员变量:self.Worker = myThread() - 用
with语句自动管理文件资源,避免内存泄漏:
def run(self): self.rm = pyvisa.ResourceManager() # 自动关闭文件 with open("ipasa.txt", 'r') as fichier: self.ip = fichier.read().strip() # 去除多余换行符 try: self.inst = self.rm.open_resource(f"TCPIP0::{self.ip}::inst0::INSTR", timeout=25000) self.status_connect.emit(True) except Exception as e: logging.error(f'连接频谱分析仪失败: {str(e)}') self.status_connect.emit(False) # PyVisa的open_resource已有超时,无需额外sleep
4. 主程序错误修正
主程序中myApp.quit()会导致窗口显示后立即退出,需删除该语句,并补充缺失的sys模块导入:
from PyQt5 import QtWidgets import sys from baa_connexion import BAA myApp = QtWidgets.QApplication(sys.argv) myApp.setStyle('Fusion') window = BAA() window.show() myApp.exec_()
最终效果
修改后,点击按钮会弹出显示“Connection....”的模态对话框,后台线程执行PyVisa连接操作,连接完成后对话框自动关闭,主窗口标签会显示对应的连接结果。
内容的提问来源于stack exchange,提问作者AlexM
相关产品推荐
相关产品推荐

