简体   繁体   English

如何与asyncio同时运行无限循环?

[英]How to concurrently run a infinite loop with asyncio?

How to continue to next loop when awaiting? 等待时如何继续下一个循环? For example: 例如:

async def get_message():
    # async get message from queue
    return message

async process_message(message):
    # make some changes on message
    return message

async def deal_with_message(message):
    # async update some network resource with given message

async def main():
    while True:
        message = await get_message()
        message = await process_message(message)
        await deal_with_message(message)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

How can I make the while True loop concurrent? 如何使while True循环并发? If it is awaiting deal_with_message , it can go to the next loop and run get_message ? 如果它正在等待deal_with_message ,它可以进入下一个循环并运行get_message

Edited 编辑

I think I have found a solution: 我想我找到了一个解决方案:

async def main():
    asyncio.ensure_future(main())
    message = await get_message()
    message = await process_message(message)
    await deal_with_message(message)

loop = asyncio.get_event_loop()
asyncio.ensure_future(main())
loop.run_forever()

Your solution will work, however I see problem with it. 您的解决方案将起作用,但我发现它存在问题。

async def main():
    asyncio.ensure_future(main())
    # task finishing

As soon as main started it creates new task and it happens immediately ( ensure_future creates task immediately) unlike actual finishing of this task that takes time. 一旦main启动它就会立即创建新任务并立即发生( ensure_future立即创建任务),这与需要时间的此任务的实际完成不同。 I guess it can potentially lead to creating enormous amount of tasks which can drain your RAM. 我想这可能会导致创造大量的任务,从而耗尽你的RAM。

Besides that it means that potentially any enormous amount of tasks can be ran concurrently. 除此之外,它意味着可以同时运行任何大量的任务。 It can drain your network throughput or amount of sockets that can be opened same time (just imagine you're tying to download 1 000 000 urls parallely - nothing good will happen). 它可以消耗你的网络吞吐量或可以同时打开的套接字数量(想象一下你并不想平行下载1 000 000个网址 - 没有什么好事会发生)。

In concurrent world this problem usually can be solved by limiting amount of things that can be ran concurrently with some sensible value using something like Semaphore . 在并发世界中,这个问题通常可以通过使用信号量之类的东西来限制可以与某些合理价值同时运行的事物的数量来解决 In your case however I think it's more convenient to track amount of running tasks manually and populate it manually: 但是在我的情况下,我认为手动跟踪运行任务的数量并手动填充它更方便:

import asyncio
from random import randint


async def get_message():
    message = randint(0, 1_000)
    print(f'{message} got')
    return message


async def process_message(message):
    await asyncio.sleep(randint(1, 5))
    print(f'{message} processed')
    return message


async def deal_with_message(message):
    await asyncio.sleep(randint(1, 5))
    print(f'{message} dealt')


async def utilize_message():
    message = await get_message()
    message = await process_message(message)
    await deal_with_message(message)


parallel_max = 5  # don't utilize more than 5 msgs parallely
parallel_now = 0


def populate_tasks():
    global parallel_now
    for _ in range(parallel_max - parallel_now):
        parallel_now += 1
        task = asyncio.ensure_future(utilize_message())
        task.add_done_callback(on_utilized)


def on_utilized(_):
    global parallel_now
    parallel_now -= 1
    populate_tasks()


if __name__ ==  '__main__':
    loop = asyncio.get_event_loop()
    try:
        populate_tasks()
        loop.run_forever()
    finally:
        loop.run_until_complete(loop.shutdown_asyncgens())
        loop.close()

Output will be like: 输出将如下:

939 got
816 got
737 got
257 got
528 got
939 processed
816 processed
528 processed
816 dealt
589 got
939 dealt
528 dealt
712 got
263 got
737 processed
257 processed
263 processed
712 processed
263 dealt
712 dealt
386 got
708 got
589 processed
257 dealt
386 processed
708 processed
711 got
711 processed

Important part here is how we got next message to be utilized only after amount of running tasks decreased to less than five. 这里重要的部分是我们如何在运行任务量减少到少于5之后才能使用下一条消息。

Upd: UPD:

Yes, semaphore seems to be more convenient if you don't need to change max running number dynamically. 是的,如果您不需要动态更改最大运行数,信号量似乎更方便。

sem = asyncio.Semaphore(5)


async def main():
    async with sem:
        asyncio.ensure_future(main())
        await utilize_message()


if __name__ ==  '__main__':
    loop = asyncio.get_event_loop()
    try:
        asyncio.ensure_future(main())
        loop.run_forever()
    finally:
        loop.run_until_complete(loop.shutdown_asyncgens())
        loop.close()

The easiest solution is asyncio.ensure_future . 最简单的解决方案是asyncio.ensure_future

async def main():
    tasks = []
    while running:
        message = await get_message()
        message = await process_message(message)
        coroutine = deal_with_message(message)
        task = asyncio.ensure_future(coroutine) # starts running coroutine
        tasks.append(task)
    await asyncio.wait(tasks)

Keeping track of the tasks yourself is optional if all your tasks can be awaited at the end. 如果可以在最后等待所有任务,那么自己跟踪任务是可选的。

async def main():
    while running:
        message = await get_message()
        message = await process_message(message)
        coroutine = deal_with_message(message)
        asyncio.ensure_future(coroutine)
    tasks = asyncio.Task.all_tasks()
    await asyncio.wait(tasks)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM