PySide6程序多实例启动问题及窗口无响应修复咨询
单实例PySide6程序实现:避免多实例启动并唤醒后台窗口
问题背景
开发的PySide6程序已实现Windows托盘图标功能:双击托盘可恢复主窗口,关闭窗口后保留托盘且能回到之前操作状态。但双击程序执行文件多次会启动多实例(托盘出现多个图标),需求是:再次双击执行文件时不启动新实例,直接将后台运行的程序窗口前置显示。
尝试通过本地端口检测实现该功能:启动时检查指定端口是否被占用,若占用则发送信号让后台程序恢复窗口,但触发后窗口出现无响应(双击托盘恢复则正常),推测是线程处理问题导致。
核心代码片段
主程序代码
if __name__ == "__main__": root = QApplication(sys.argv) app = HomeView() app.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowTitleHint | Qt.CustomizeWindowHint) app = HomeController(app, HomeModel()) root.setQuitOnLastWindowClosed(False) icon = QIcon("interface/images/icon.ico") tray = QSystemTrayIcon() tray.setIcon(icon) tray.setVisible(True) tray.activated.connect(app.show) root.exec_()
HomeController代码
class HomeController(Controller): def __init__(self, view, model, msg=None) -> None: super().__init__(view, model, msg=None) self.chamados_controller = None self.abrir_chamado_controller = None if not self.verificar_tokens(): self._controllerToken.show() else: self.start() self._view = view self._model = model self.tray_icon = QSystemTrayIcon(QIcon("icon.ico"), self._view) self.tray_icon.setToolTip("HUB") self.tray_icon.activated.connect(self.tray_icon_activated) self.tray_icon.show() def tray_icon_activated(self, reason): if reason == QSystemTrayIcon.DoubleClick: if self._view.isHidden() or self._view.isMinimized(): self._view.show() self._view.raise_() self._view.activateWindow() def closeEvent(self, event): event.ignore() self.hide() self.tray_icon.showMessage("Hub", "Rodando em segundo plano", QIcon.Information, 2000) def start(self): self.showMaximized() self.iniciar_sessao() self.definir_user() self.definir_maquina() self.chamados_controller = ChamadosController( ChamadosView(), ChamadoModel(self._model.api), self._view.lb_msg, ) self.abrir_chamado_controller = AbrirChamadoController( AbrirChamadoView(), AbrirChamadoModel(self._model.api), self._view.lb_msg, self, ) self._telas = { 1: self.chamados_controller, 2: self.abrir_chamado_controller, } self.definir_telas() self._view.btn_chamados.clicked.connect( lambda: self.navegar(1, "Chamados") ) self._view.btn_abrirChamado.clicked.connect( lambda: self.navegar(2, "Abrir Chamado") ) self.go_to_default_screen() # Conectando os sinais dos botões às funções correspondentes self._view.btn_minimize.clicked.connect(self.minimize_window) self._view.btn_close.clicked.connect(self.close_window) self._view.exit.clicked.connect(self.close_window) self._view.btn_maximize_restore.clicked.connect(self.maximize_restore_window) self._view.btn_toggle_menu.clicked.connect(self.menu)
尝试的本地服务器代码
def __init__(self, view, model, msg=None) -> None: super().__init__(view, model, msg=None) self.chamados_controller = None self.abrir_chamado_controller = None if not self.verificar_tokens(): self._controllerToken.show() else: self.start() self._view = view self._model = model self.tray_icon = QSystemTrayIcon(QIcon("icon.ico"), self._view) self.tray_icon.setToolTip("Client Help Desk") self.tray_icon.activated.connect(self.tray_icon_activated) self.tray_icon.show() server_thread = threading.Thread(target=self.start_server) server_thread.start() def start_server(self): host = 'localhost' port = 37548 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((host, port)) server_socket.listen(5) print(f"Servidor escutando em {host}:{port}") while True: client_socket, client_address = server_socket.accept() print(f"Conexão aceita de {client_address[0]}:{client_address[1]}") if self._view.isHidden() or self._view.isMinimized(): self._view.show() self._view.raise_() self._view.activateWindow() client_socket.close()
解决方案:线程安全的单实例实现
问题根源
Qt要求所有UI操作必须在主线程执行,你之前的代码直接在服务器子线程中调用self._view.show()等UI方法,导致线程冲突,引发窗口无响应。
修改后的实现代码
import socket import threading from PySide6.QtCore import Signal, QObject from PySide6.QtWidgets import QSystemTrayIcon class HomeController(Controller): # 定义自定义信号,用于在子线程中触发主线程的UI操作 wake_up_window = Signal() def __init__(self, view, model, msg=None) -> None: super().__init__(view, model, msg=None) self.chamados_controller = None self.abrir_chamado_controller = None # 第一步:检测是否已有实例在运行 if self._check_running_instance(): # 已有实例,发送唤醒信号后退出当前进程 self._send_wake_signal() import sys sys.exit(0) if not self.verificar_tokens(): self._controllerToken.show() else: self.start() self._view = view self._model = model self.tray_icon = QSystemTrayIcon(QIcon("icon.ico"), self._view) self.tray_icon.setToolTip("HUB") self.tray_icon.activated.connect(self.tray_icon_activated) self.tray_icon.show() # 连接信号到主线程的UI处理函数 self.wake_up_window.connect(self._bring_window_to_front) # 启动服务器守护线程 server_thread = threading.Thread(target=self._start_server) server_thread.daemon = True # 设置为守护线程,主程序退出时自动终止 server_thread.start() def _check_running_instance(self) -> bool: """检查指定端口是否被占用,判断是否已有实例运行""" host = 'localhost' port = 37548 try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.close() return True except ConnectionRefusedError: return False def _send_wake_signal(self): """向已有实例发送唤醒信号""" host = 'localhost' port = 37548 try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((host, port)) s.send(b'wake') # 发送任意数据触发唤醒 s.close() except Exception as e: print(f"发送唤醒信号失败: {e}") def _start_server(self): """启动本地服务器监听唤醒信号""" host = 'localhost' port = 37548 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((host, port)) server_socket.listen(5) print(f"服务器监听 {host}:{port}") while True: client_socket, client_address = server_socket.accept() print(f"接受连接来自 {client_address[0]}:{client_address[1]}") # 读取客户端发送的数据(仅触发作用) client_socket.recv(1024) # 发射信号,让主线程处理UI操作 self.wake_up_window.emit() client_socket.close() def _bring_window_to_front(self): """主线程中执行的窗口前置逻辑,线程安全""" if self._view.isHidden() or self._view.isMinimized(): self._view.show() self._view.raise_() self._view.activateWindow() # 原有托盘双击逻辑复用同一个UI处理函数 def tray_icon_activated(self, reason): if reason == QSystemTrayIcon.DoubleClick: self._bring_window_to_front() # 其余原有方法保持不变...
关键改进点
- 线程安全的UI操作:通过Qt的
Signal机制,将子线程中的唤醒请求传递到主线程执行,避免直接在子线程操作UI控件 - 完整的单实例检测:启动时先尝试连接指定端口,成功则说明已有实例,发送信号后退出当前进程
- 守护线程设置:服务器线程设为守护线程,确保主程序退出时线程自动终止,避免残留进程
内容的提问来源于stack exchange,提问作者srliath
相关产品推荐
相关产品推荐

