1 问题描述
当我在一个循环事件中创建三个异步函数时,其中两个作为生产者,余下一个作为消费者进行无限循环运行,他们之间通过异步消息队列 asyncio.Queue() 进行通信,代码如下:
import asyncio
from asyncio import sleep
queue1 = asyncio.Queue()
async def run_async_tasks():
task1 = asyncio.create_task(producer_task_1())
task2 = asyncio.create_task(producer_task_2())
task3 = asyncio.create_task(consumer_task())
await asyncio.gather(task1, task2, task3)
async def producer_task_1():
item = ("message", "message from producer_task_1")
while True:
await queue1.put(item)
print("producer_task_1:produce message")
await sleep(2)
async def producer_task_2():
item = ("message", "message from producer_task_2")
while True:
await queue1.put(item)
print("producer_task_2:produce message")
await sleep(4)
async def consumer_task():
while True:
info = await queue1.get()
print(f"consume message: {info}")
asyncio.run(run_async_tasks())
很遗憾的是,发生了如下报错:
RuntimeError: Task <Task pending name='Task-4' coro=<consumer_task() running at E:\harrylin\pythonCode\pnt-server\test\queueTest.py:40> cb=[_gather.<locals>._done_callback() at D:\ProgramData\Anaconda3\envs\test03\lib\asyncio\tasks.py:767]> got Future <Future pending> attached to a different loop
2 问题分析
作者反复确认代码,此三个异步任务是在同一个循环中的,为啥会报错 different loop 。通过分析发现,这个错误信息并非是说这三个异步任务不在同一个循环中,而是消息队列不在这三个异步任务的循环中。由于消息队列的初始化是在异步任务外部进行,可能会导致该消息队列被绑定到与异步任务不同的事件循环。这是因为在 asyncio.run(run_async_tasks()) 调用之前,全局变量初始化的队列可能没有被绑定到任何事件循环,或者被绑定到默认的事件循环。
3 问题解决
知道问题的原因后,要解决此问题也简单,只要确保消息队列在异步函数内部初始化,并在同一个事件循环中访问即可。根据以上思想,作者给出两个解决方案:
3.1 全局变量方案
方案1 消息队列依然作为全局变量,但在异步函数中进行初始化
import asyncio
from asyncio import sleep
queue1 = None # 全局变量声明定义,但不进行初始化
async def run_async_tasks():
global queue1 # 声明全局变量
# 在异步函数中进行初始化,保证异步函数执行时才被初始化,从而能够绑定在同一个循环事件中
queue1 = asyncio.Queue()
task1 = asyncio.create_task(producer_task_1())
task2 = asyncio.create_task(producer_task_2())
task3 = asyncio.create_task(consumer_task())
await asyncio.gather(task1, task2, task3)
# 以下代码保持不变
async def producer_task_1():
item = ("message", "message from producer_task_1")
while True:
await queue1.put(item)
print("producer_task_1:produce message")
await sleep(2)
async def producer_task_2():
item = ("message", "message from producer_task_2")
while True:
await queue1.put(item)
print("producer_task_2:produce message")
await sleep(4)
async def consumer_task():
while True:
info = await queue1.get()
print(f"consume message: {info}")
asyncio.run(run_async_tasks())
3.2 局部变量方案
方案2 消息队列作为全局变量,在 async def run_async_tasks() 中进行声明和初始化,并作为入参传递到各个异步任务中。优化后的全量代码如下:
但在异步函数中进行初始化
import asyncio
from asyncio import sleep
async def run_async_tasks():
queue1 = asyncio.Queue()
task1 = asyncio.create_task(producer_task_1(queue1))
task2 = asyncio.create_task(producer_task_2(queue1))
task3 = asyncio.create_task(consumer_task(queue1))
await asyncio.gather(task1, task2, task3)
async def producer_task_1(queue1):
item = ("message", "message from producer_task_1")
while True:
await queue1.put(item)
print("producer_task_1:produce message")
await sleep(2)
async def producer_task_2(queue1):
item = ("message", "message from producer_task_2")
while True:
await queue1.put(item)
print("producer_task_2:produce message")
await sleep(4)
async def consumer_task(queue1):
while True:
info = await queue1.get()
print(f"consume message: {info}")
asyncio.run(run_async_tasks())