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

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替代手动创建循环,更简洁

服务端关键改动:

  1. 移除await timertask:用asyncio.create_task()启动定时器后,直接保存任务引用,让它在事件循环后台运行,不阻塞当前协程。
  2. 调整逻辑顺序:先读取客户端数据,再判断命令,避免原代码中初始data=None导致的逻辑混乱。
  3. quit时取消定时器:保存timer_task引用,收到quit命令时调用cancel()停止定时器,并用await处理取消后的收尾。
  4. 用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())

客户端关键改动:

  1. 移除冗余的quit发送:原代码中输入quit后已经发送过一次,不需要再写writer.write(b"quit")。
  2. 用break替代exit:exit会直接终止程序,用break能让代码正常执行关闭连接的逻辑,更优雅。
  3. 添加finally块:确保无论是否出现异常,连接都会被正确关闭。

内容的提问来源于stack exchange,提问作者nunodsousa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 06:21:00