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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 13:57:18