Python Asyncio带Timer的服务端阻塞问题求解
问题分析与解决方案
你的代码里定时器阻塞的核心原因是:启动定时器任务后直接await timertask。timer()是一个无限循环的协程,await会一直等待它完成,导致handle_echo协程被永久挂起,无法继续处理客户端的后续请求(包括quit命令),服务器和客户端自然就卡住了。
同时你的代码还有其他逻辑问题需要调整,比如命令判断顺序、quit时未停止定时器、客户端quit处理冗余等,以下是完整的修正方案:
修正后的服务端代码
import asyncio HOST = '127.0.0.1' PORT = 9999 async def timer(): while True: print('tick') await asyncio.sleep(1) async def handle_echo(reader: asyncio.StreamReader, writer: asyncio.StreamWriter) -> None: '''Handle the echo protocol.''' timer_task = None # 保存定时器任务引用,用于后续取消 try: while True: # 先读取客户端数据,再处理命令(修正原逻辑顺序问题) data = await reader.read(1024) if not data: break # 客户端主动断开连接 msg = data.decode().strip() # 去除可能的换行/空格 addr, port = writer.get_extra_info('peername') print(f"Received {msg!r} from {addr}:{port!r}") if msg == 'quit': # 收到quit命令时,先取消定时器(如果存在) if timer_task and not timer_task.done(): timer_task.cancel() try: await timer_task # 等待任务取消完成 except asyncio.CancelledError: print("Timer stopped") # 关闭连接并退出循环 writer.close() await writer.wait_closed() break elif msg == 'timer': # 启动定时器任务,不await,让它在后台运行 if not timer_task or timer_task.done(): timer_task = asyncio.create_task(timer()) send_message = 'Timer started' else: send_message = 'Timer is already running' else: # 普通回显逻辑 send_message = f'Message received: {msg}' # 发送响应给客户端 writer.write(send_message.encode()) await writer.drain() except asyncio.CancelledError: # 处理协程被取消的情况 if timer_task and not timer_task.done(): timer_task.cancel() await timer_task async def run_server() -> None: server = await asyncio.start_server(handle_echo, HOST, PORT) async with server: await server.serve_forever() if __name__ == '__main__': asyncio.run(run_server()) # 用asyncio.run替代手动创建循环,更简洁
服务端关键改动:
- 移除
await timertask:用asyncio.create_task()启动定时器后,直接保存任务引用,让它在事件循环后台运行,不阻塞当前协程。 - 调整逻辑顺序:先读取客户端数据,再判断命令,避免原代码中初始
data=None导致的逻辑混乱。 - quit时取消定时器:保存
timer_task引用,收到quit命令时调用cancel()停止定时器,并用await处理取消后的收尾。 - 用
asyncio.run()简化循环启动:替代手动创建事件循环的写法,符合Python 3.7+的最佳实践。
修正后的客户端代码
import asyncio HOST = '127.0.0.1' PORT = 9999 async def run_client() -> None: reader, writer = await asyncio.open_connection(HOST, PORT) try: while True: message = input('Enter a message: ') writer.write(message.encode()) await writer.drain() data = await reader.read(1024) if not data: print("Server disconnected") break print(f"Received {data.decode()!r}") if message == 'quit': # 不需要重复发送quit,直接关闭连接 writer.close() await writer.wait_closed() break except Exception as e: print(f"Error: {e}") finally: # 确保连接被关闭 if not writer.is_closing(): writer.close() await writer.wait_closed() if __name__ == '__main__': asyncio.run(run_client())
客户端关键改动:
- 移除冗余的quit发送:原代码中输入quit后已经发送过一次,不需要再写
writer.write(b"quit")。 - 用break替代exit:exit会直接终止程序,用break能让代码正常执行关闭连接的逻辑,更优雅。
- 添加finally块:确保无论是否出现异常,连接都会被正确关闭。
内容的提问来源于stack exchange,提问作者nunodsousa
相关产品推荐
相关产品推荐

