asyncio.PriorityQueue是最直接的选择,它线程安全、协程友好,基于heapq实现O(log n)排序,优先级值越小越先执行,但需手动取任务并await执行,不自动调度。

asyncio.PriorityQueue 是最直接的选择
Python 标准库的 asyncio.PriorityQueue 就是为这种场景设计的:它线程安全、协程友好,且底层基于 heapq,支持 O(log n) 入队和出队。但它不是“开箱即用”的任务调度器——它只管排序和等待,不负责执行任务。
常见错误是直接把 coro 对象塞进队列然后期望自动运行。实际上你得手动从队列取、await 执行,并处理异常和重试逻辑。
使用时注意:PriorityQueue 的优先级值越小越先执行,且必须是可比较类型(通常是 int 或 tuple)。比如紧急任务用 0,普通任务用 10,后台任务用 100。
示例片段:
import asyncio
<p>queue = asyncio.PriorityQueue()
await queue.put((0, "send_alert", {"user_id": 123}))
await queue.put((10, "sync_profile", {"user_id": 456}))</p><p>priority, task_name, kwargs = await queue.get()</p><h1>然后手动调用对应协程</h1><p></p>
如何避免 await queue.get() 阻塞整个调度器
单个 await queue.get() 会挂起当前协程,但如果你只用一个消费者,高优先级任务来了也得等前面的任务执行完才能被取出——这不是真优先级调度,只是“优先入队”。要真正响应优先级,得让多个消费者并发等待,或用非阻塞轮询 + asyncio.wait_for 控制超时。
更稳妥的做法是启动固定数量的工作协程(比如 3 个),每个都循环 await queue.get(),并用 asyncio.create_task() 启动实际任务。这样高优任务一旦入队,下一个空闲 worker 就能立刻取到它。
容易踩的坑:
- worker 数量设太多可能压垮下游服务(比如数据库连接池)
- 没加
try/except导致某个任务崩溃后整个 worker 退出,后续任务卡住 - 忘记调用
queue.task_done(),导致join()永远不返回
任务重复入队和状态管理怎么处理
PriorityQueue 本身不支持去重或状态查询。如果同一用户资料同步任务因网络抖动被反复触发,可能堆积多个相同 (10, "sync_profile", {"user_id": 456}) 入队。
可行方案是在入队前加一层轻量缓存,比如用 dict 记录 "sync_profile:456" → True,并在任务执行完毕后清除。注意这个 dict 要线程安全,推荐用 asyncio.Lock 包裹写操作,或直接用 asyncio.Queue 做任务预检通道。
另一个现实问题是:任务执行中崩溃了,是否重试?重试几次?这和优先级有关——紧急告警失败应立即重试(甚至提高优先级),而日志上报失败可以降级为低优任务。这些逻辑不能靠队列本身,得在取任务后的 handler 里显式判断。
为什么不用第三方库如 aio-pika 或 Celery
如果任务需要跨进程、持久化、Web 管理界面或死信队列,那 asyncio.PriorityQueue 确实不够用。但很多内部服务调度(如 API 网关的限流回调、IoT 设备指令排队)只需要内存级、单进程、毫秒级响应——这时引入 RabbitMQ 或 Redis 会显著增加部署复杂度和延迟。
Celery 默认同步执行模型,虽支持 async,但它的 worker 进程模型和序列化开销对纯协程任务反而成了瓶颈。真正需要的是轻量、可控、无外部依赖的调度内核,PriorityQueue 搭配几个 asyncio.create_task 就够用。
关键提醒:别为了“优先级”强行套用复杂方案。先确认你的任务是否真的存在优先级冲突(比如 99% 的任务都是同优先级),再决定要不要上锁、去重、持久化——多数时候,一个带 Lock 的字典 + PriorityQueue + 3 个 worker 就跑得稳。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











