Python asyncio/aiohttp-create_task()阻塞事件循环,将结果收集到";此事件循环已在运行;

Python asyncio/aiohttp-create_task()阻塞事件循环,将结果收集到";此事件循环已在运行;,python,python-asyncio,coroutine,aiohttp,event-loop,Python,Python Asyncio,Coroutine,Aiohttp,Event Loop,我无法让消费者和生产者同时运行,似乎worker()或aiohttp服务器正在阻塞—即使与asyncio.gather()同时执行时也是如此 相反,如果我执行loop.create_任务(worker),这将阻塞,服务器将永远不会启动 我已经尝试了我能想象到的每一种变化,包括nest_asyncio模块——我只能运行这两个组件中的一个 我做错了什么 async def worker(): batch_size = 30 print("running worker&qu

我无法让消费者和生产者同时运行,似乎worker()或aiohttp服务器正在阻塞—即使与asyncio.gather()同时执行时也是如此

相反,如果我执行loop.create_任务(worker),这将阻塞,服务器将永远不会启动

我已经尝试了我能想象到的每一种变化,包括nest_asyncio模块——我只能运行这两个组件中的一个

我做错了什么

async def worker():
    batch_size = 30

    print("running worker")
    while True:
        if queue.qsize() > 0:
            future_map = {}

            size = min(queue.qsize(), batch_size)
            batch = []
            for _ in range(size):
                item = await queue.get()
                print("Item: "+str(item))
                future_map[item["fname"]] = item["future"]
                batch.append(item)

            print("processing", batch)
            results = await process_files(batch)
            for dic in results:
                for key, value in dic.items():
                    print(str(key)+":"+str(value))
                    future_map[key].set_result(value)

            # mark the tasks done
            for _ in batch:
                queue.task_done()



def start_worker():
    loop.create_task(worker())

def create_app():
    app = web.Application()
    routes = web.RouteTableDef()
    @routes.post("/decode")
    async def handle_post(request):
        return await decode(request)
    app.add_routes(routes)
    app.on_startup.append(start_worker())
    return app

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    queue = asyncio.Queue()
    app = create_app()
    web.run_app(app)
上面打印的是“正在运行的worker”,并不启动AIOHTTP服务器

def run(loop, app, port=8001):
handler = app.make_handler()
f = loop.create_server(handler, '0.0.0.0', port)
srv = loop.run_until_complete(f)
print('serving on', srv.sockets[0].getsockname())
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass
finally:
    loop.run_until_complete(handler.finish_connections(1.0))
    srv.close()
    loop.run_until_complete(srv.wait_closed())
    loop.run_until_complete(app.finish())
loop.close()

def main(app):
    asyncio.gather(run(loop, app), worker())

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    queue = asyncio.Queue()
    app = create_app()
    main(app)

上述方法启动服务器,但不启动工作进程。

尽管
等待异步。sleep(0)
解决了当前问题,但它不是一个理想的解决方案;事实上,这有点反模式。为了了解原因,让我们更详细地检查问题发生的原因。问题的核心是工人的
while
循环-一旦队列变空,它实际上归结为:

while True:
    pass
当然,标记为
pass
的部分包含对
qsize()
的检查,如果队列非空,则会导致执行额外的代码,但一旦
qsize()
首次达到0,该检查将始终计算为false。这是因为asyncio是单线程的,当
qsize()==0时,
while
循环不再遇到单个
wait
。如果没有
wait
,就不可能将控制权交给可能填充队列的协程或回调,而
while
循环将变得无限

这就是为什么
await asyncio.sleep(0)
在循环中有帮助:它强制进行上下文切换,确保其他协同路由有机会运行并最终重新填充队列。但是,它也会在循环持续运行时保持
,这意味着事件循环将永远不会进入睡眠状态,即使队列连续数小时保持为空。只要工作进程处于活动状态,事件循环就会保持忙碌等待状态。正如dirn所建议的,您可以通过将睡眠间隔调整为非零值来缓解繁忙的等待,但这将引入延迟,并且在没有活动时仍不允许事件循环进入睡眠状态

正确的解决方法是不检查
qsize()
,而是使用
queue.get()
获取下一项。这将根据需要休眠到项目出现为止,并在项目出现后立即唤醒协同程序。不要担心这会“阻塞”工作进程——asyncio的关键就是您可以有多个协同路由,而在等待时“阻塞”一个只允许其他人继续。例如:

async def worker():
    batch_size = 30

    while True:
        # wait for an item and add it to the batch
        batch = [await queue.get()]
        # batch up more items if available
        while not queue.empty() and len(batch) < batch_size:
            batch.append(await queue.get())
        # process the batch
        future_map = {item["fname"]: item["future"] for item in batch}
        results = await process_files(batch)
        for dic in results:
            for key, value in dic.items():
                print(str(key)+":"+str(value))
                future_map[key].set_result(value)
        for _ in batch:
            queue.task_done()
async def worker():
批量大小=30
尽管如此:
#等待项目并将其添加到批次中
batch=[wait queue.get()]
#如果有更多项目,请批量处理
而不是queue.empty()和len(batch)

在这个变体中,我们在循环的每次迭代中都会等待一些东西,而不需要睡眠。

尽管
等待异步IO。睡眠(0)
解决了直接的问题,但它不是一个理想的解决方案;事实上,这有点反模式。为了了解原因,让我们更详细地检查问题发生的原因。问题的核心是工人的
while
循环-一旦队列变空,它实际上归结为:

while True:
    pass
当然,标记为
pass
的部分包含对
qsize()
的检查,如果队列非空,则会导致执行额外的代码,但一旦
qsize()
首次达到0,该检查将始终计算为false。这是因为asyncio是单线程的,当
qsize()==0时,
while
循环不再遇到单个
wait
。如果没有
wait
,就不可能将控制权交给可能填充队列的协程或回调,而
while
循环将变得无限

这就是为什么
await asyncio.sleep(0)
在循环中有帮助:它强制进行上下文切换,确保其他协同路由有机会运行并最终重新填充队列。但是,它也会在循环持续运行时保持
,这意味着事件循环将永远不会进入睡眠状态,即使队列连续数小时保持为空。只要工作进程处于活动状态,事件循环就会保持忙碌等待状态。正如dirn所建议的,您可以通过将睡眠间隔调整为非零值来缓解繁忙的等待,但这将引入延迟,并且在没有活动时仍不允许事件循环进入睡眠状态

正确的解决方法是不检查
qsize()
,而是使用
queue.get()
获取下一项。这将根据需要休眠到项目出现为止,并在项目出现后立即唤醒协同程序。不要担心这会“阻塞”工作进程——asyncio的关键就是您可以有多个协同路由,而在等待时“阻塞”一个只允许其他人继续。例如:

async def worker():
    batch_size = 30

    while True:
        # wait for an item and add it to the batch
        batch = [await queue.get()]
        # batch up more items if available
        while not queue.empty() and len(batch) < batch_size:
            batch.append(await queue.get())
        # process the batch
        future_map = {item["fname"]: item["future"] for item in batch}
        results = await process_files(batch)
        for dic in results:
            for key, value in dic.items():
                print(str(key)+":"+str(value))
                future_map[key].set_result(value)
        for _ in batch:
            queue.task_done()
async def worker():
批量大小=30
尽管如此:
#等待项目并将其添加到批次中
batch=[wait queue.get()]
#如果有更多项目,请批量处理
而不是queue.empty()和len(batch)