You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()是模态对话框,会阻塞当前线程的事件循环,直到对话框被关闭。

二、原始代码的核心问题

  1. 错误调用线程方法:self.status_thread = self.Worker.connexion_asa()直接调用线程类方法,导致PyVisa连接操作在主线程执行,主线程被阻塞,弹窗代码self.qdiag.exec()根本没机会运行,直到连接完成才会执行弹窗。
  2. 未重写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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 01:48:38