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

如何使用asyncio.serve_forever()且不冻结PyQt5 GUI?

PyQt5中避免TCP服务器启动阻塞GUI的解决方案

你的推测完全正确——server.serve_forever()会一直阻塞当前线程,而PyQt的GUI运行在主线程,一旦主线程被阻塞,就会出现界面冻结无响应的情况。下面是两种实用的解决方法:

方法一:用线程分离TCP服务器运行逻辑

把TCP服务器的事件循环放到单独的线程中执行,让GUI主线程专注处理用户交互。

1. 修改tcp.py,添加线程调用的包装函数

import asyncio

async def handle_client(reader, writer):
    # 这里实现你的客户端处理逻辑
    data = await reader.read(100)
    message = data.decode()
    addr = writer.get_extra_info('peername')
    print(f"Received {message!r} from {addr!r}")

    writer.write(data)
    await writer.drain()

    writer.close()
    await writer.wait_closed()

async def tcp_server_connect():
    server = await asyncio.start_server(
        handle_client, '127.0.0.1', 8888)
    async with server:
        await server.serve_forever()

def run_tcp_server():
    # 为线程创建独立的asyncio事件循环并运行服务器
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    loop.run_until_complete(tcp_server_connect())
    loop.close()

2. 在GUI代码中调用线程启动服务器

from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
import sys
import threading
from tcp import run_tcp_server

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()

    def init_ui(self):
        self.connect_btn = QPushButton("连接服务器", self)
        self.connect_btn.clicked.connect(self.on_connect_clicked)
        self.setCentralWidget(self.connect_btn)
        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('TCP服务器GUI')
        self.show()

    def on_connect_clicked(self):
        # 创建守护线程运行服务器,程序退出时自动终止线程
        self.server_thread = threading.Thread(target=run_tcp_server, daemon=True)
        self.server_thread.start()

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec_())

方法二:集成PyQt与asyncio事件循环

让asyncio的事件循环和PyQt的GUI事件循环协同工作,无需额外线程。

修改GUI代码如下

from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton
from PyQt5.QtCore import QTimer
import sys
import asyncio
from tcp import tcp_server_connect

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.init_ui()
        self.loop = asyncio.get_event_loop()
        # 用定时器定期处理asyncio事件循环的任务
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.process_async_tasks)
        self.timer.start(10)

    def init_ui(self):
        self.connect_btn = QPushButton("连接服务器", self)
        self.connect_btn.clicked.connect(self.on_connect_clicked)
        self.setCentralWidget(self.connect_btn)
        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('TCP服务器GUI')
        self.show()

    def process_async_tasks(self):
        # 每次定时器触发时,处理asyncio的待执行任务
        self.loop.call_soon(self.loop.stop)
        self.loop.run_forever()

    def on_connect_clicked(self):
        # 将服务器启动任务提交到asyncio事件循环
        asyncio.ensure_future(tcp_server_connect(), loop=self.loop)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainWindow()
    sys.exit(app.exec_())

方法推荐

优先选择方法一,逻辑更直观,线程隔离能避免很多跨事件循环的潜在问题,尤其适合新手快速解决问题。

内容的提问来源于stack exchange,提问作者Barış Şenol

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 17:03:28