Python并行运行websockets服务与PySimpleGUI GUI循环阻塞问题
问题根因
PySimpleGUI 对线程有强制约束:所有GUI相关操作(包括window.read()调用、控件内容更新)必须在主线程执行,不允许放到子线程运行。
原代码把GUI循环updateGui放到子线程启动,主线程用来运行asyncio的WebSocket服务,直接违反了这个约束,才会导致window.read()异常阻塞,仅在窗口关闭时才返回。
除此之外原代码还存在线程安全隐患:WebSocket逻辑和GUI逻辑同时读写共享的players列表,没有做任何同步保护,就算GUI能正常运行,后续也会偶发数据错乱、程序崩溃。
修复方案
调整线程分工,把asyncio的WebSocket服务放到子线程运行,主线程专门处理GUI事件循环,同时用线程安全的队列做两个线程间的数据传递,禁止跨线程直接操作GUI控件、无锁访问共享变量。
修复后可运行示例代码
import asyncio import json import websockets import PySimpleGUI as sg import threading import queue from typing import List, Dict # 线程安全队列:WebSocket线程往队列扔更新数据,GUI线程从队列取数据刷新界面 gui_update_queue = queue.Queue() # 玩家数据仅在WebSocket线程内做写操作,GUI线程不直接访问 players: List[Dict] = [] async def gameLogicHandler(event, ws): <handle client input> # 业务逻辑处理完如果玩家状态有变化,把最新玩家名列表推到队列通知GUI更新 gui_update_queue.put([p["name"] for p in players]) async def handler(websocket): # 新玩家连接逻辑示例 players.append({"name": f"Player_{len(players)+1}", "ws": websocket}) gui_update_queue.put([p["name"] for p in players]) while True: try: message = await websocket.recv() except websockets.ConnectionClosedOK: # 玩家断开连接时清理数据,通知GUI更新 for p in players: if p["ws"] == websocket: players.remove(p) break gui_update_queue.put([p["name"] for p in players]) break await gameLogicHandler(json.loads(message), websocket) layout = [ [sg.Text('Catan Server', size=(20, 1), justification='center', font='Helvetica 20')], [sg.Text('Players:')], [sg.Multiline(size=(20, 10), key='players', disabled=True)] ] window = sg.Window('Catan Server', layout) async def main_socket(): async with websockets.serve(handler, "", 8001): await asyncio.Future() def run_socket_server(): # 子线程内独立运行asyncio事件循环 asyncio.run(main_socket()) def main(): # 启动WebSocket服务守护线程,主程序退出时自动终止 socket_thread = threading.Thread(target=run_socket_server, daemon=True) socket_thread.start() # 主线程独占运行GUI循环 while True: # 给read设置100ms超时,避免长时间阻塞,能定时处理队列里的更新请求 event, values = window.read(timeout=100) if event == sg.WIN_CLOSED or event == 'Exit': break # 批量处理队列里所有待刷新的内容 while not gui_update_queue.empty(): try: player_names = gui_update_queue.get_nowait() window["players"].update('\n'.join(player_names)) except queue.Empty: break window.close() if __name__ == "__main__": main()
关键修复点
- 线程职责调整:主线程只处理GUI相关逻辑,WebSocket服务放到独立守护子线程运行,符合PySimpleGUI的线程规则
- 给
window.read()增加timeout=100参数,让GUI循环每100ms唤醒一次,不会永久阻塞错过界面刷新 - 用线程安全的
queue.Queue做跨线程通信,WebSocket侧只需要在数据变化时往队列推送最新内容,不需要直接操作GUI控件,彻底避免多线程竞态问题 - 移除了原子线程内直接调用
exit()的危险操作,窗口关闭时走正常销毁流程,守护线程随主进程自动退出,不会残留进程。
内容的提问来源于stack exchange,提问作者TorNato
相关产品推荐
相关产品推荐

