Python中执行异步函数后如何正确关闭WebSocket连接?
问题分析
你的核心问题是同步阻塞操作(示例里的input()、实际项目里的Textual应用)卡住了asyncio的主事件循环,导致后续的WebSocket关闭流程无法正常执行:
- 同步函数(如
input())会完全占用事件循环所在的线程,让所有异步任务(包括WebSocket的心跳、关闭握手)都无法被调度。 - 当执行到
await websocket.close()时,事件循环已经被阻塞了很长时间,远端可能已经主动断开连接,或者本地的关闭请求无法完成握手,最终导致超时。
解决方案
针对示例代码:替换同步输入为异步执行
把阻塞的input()放到单独线程中运行,避免占用asyncio事件循环,使用asyncio.to_thread()实现:
import asyncio import websockets async def connect_and_send(ip, port): uri = f"ws://{ip}:{port}" websocket = await websockets.connect(uri) await websocket.send(r"C:\Volgistics\Vlogger;Boi.txt;;None") print("Message sent.") await second_function(websocket) print("Closing the connection...") await websocket.close() async def second_function(websocket): # 将同步input放到线程执行,不阻塞事件循环 await asyncio.to_thread(input, "Press Enter to send additional message...") await websocket.send(r"C:\Volgistics\Vlogger;Boi.txt;second;None") print("Additional message sent.") ip = "192.168.3.181" port = 8888 asyncio.run(connect_and_send(ip, port))
针对实际项目中的Textual应用
Textual默认会启动自己的事件循环,直接调用run()会阻塞asyncio主循环,推荐两种兼容方式:
方式1:让Textual共享asyncio事件循环(推荐)
Textual 0.19.0+支持run_async()方法,可在当前asyncio循环中运行应用,避免阻塞:
from textual.app import App async def second_function(websocket): app = YourTextualApp() # 替换为你的Textual应用类 # 在asyncio循环中异步运行Textual await app.run_async() # 应用退出后继续执行WebSocket操作 await websocket.send("Textual app exited")
方式2:单独线程运行Textual,用队列通信
如果必须使用同步的run()方法,将Textual放到单独线程,通过asyncio.Queue实现线程与asyncio循环的安全通信:
import threading from textual.app import App import asyncio async def second_function(websocket): exit_signal = asyncio.Queue() def run_textual(): app = YourTextualApp() # 替换为你的Textual应用类 app.run() # 应用退出后向asyncio循环发送信号 asyncio.run_coroutine_threadsafe(exit_signal.put("done"), asyncio.get_running_loop()) # 启动线程运行Textual thread = threading.Thread(target=run_textual) thread.start() # 等待Textual退出信号 await exit_signal.get() thread.join() # 继续执行WebSocket操作 await websocket.send("Textual app exited")
关键注意事项
- 永远不要在异步函数中直接调用同步阻塞函数(如
input()、time.sleep()),必须用asyncio.to_thread()或异步替代方案。 - WebSocket的关闭是异步握手过程,需要事件循环正常运行才能完成,阻塞会导致握手超时。
- 跨线程操作asyncio对象时,必须用
asyncio.run_coroutine_threadsafe()保证线程安全,禁止直接在非事件循环线程中调用异步方法。
内容的提问来源于stack exchange,提问作者Gabriel Vanderklok
相关产品推荐
相关产品推荐

