简体   繁体   English

如何等待Python asyncio call_later完成所有未决

[英]How to wait for Python asyncio call_later to finish all pending

I want to write a program that utilizes call_later to signal something (or things) to happen later, which themselves might add more routines to be invoked, and then signal to the program to run until there's nothing remaining to be run. 我想编写一个程序,该程序利用call_later来发信号通知某事(或事物)稍后发生,它们本身可能会添加更多要调用的例程,然后发信号通知该程序运行,直到没有剩余要运行的东西为止。 For an example, I can do the following: 例如,我可以执行以下操作:

import  asyncio
START_RATE = 1

async def say_hi(who):
    print(f'hello {who}')

async def call_later(t, func, *params):
    await asyncio.sleep(t)
    await func(*params)

def run():
    # Let's go!
    incr = START_RATE
    loop = asyncio.get_event_loop()
    tasks = []
    for x in range(5):
        wait_delta = 5 - x
        tasks.append(
            call_later(incr * wait_delta, say_hi, x)
        )

    loop.run_until_complete(asyncio.gather(*tasks))

run()

But you'll notice that call_later is a bespoke coroutine. 但是您会注意到call_later是定制的协程。

Is it possible to use the event loop's call_later , but somehow inspect the event loop or otherwise await the completion of all pending callbacks? 是否可以使用事件循环的call_later ,但是以某种方式检查事件循环或以其他方式等待所有未完成的回调的完成?

You may introspect using asyncio.all_tasks() but it's probably the wrong way to do it. 您可能会使用asyncio.all_tasks()进行内省,但这可能是错误的方法。

The right way would be to create futures and allow call_later to mark them as done: 正确的方法是创建期货并允许call_later将其标记为已完成:

import asyncio
from functools import partial
START_RATE = 1


def say_hi(who):
    print(f'hello {who}')


def run_on_finish(callback):
    def wrapper(func, *args, **kwargs):
        try:
            return func(*args, **kwargs)
        finally:
            callback()
    return wrapper


def release_waiter(waiter, *args):
    """Taken from standard library"""
    if not waiter.done():
        waiter.set_result(None)


def run():
    # Let's go!
    incr = START_RATE
    loop = asyncio.get_event_loop()
    tasks = []
    for x in range(5):
        wait_delta = 5 - x

        # Create a waiter
        waiter = loop.create_future()
        release_cb = partial(release_waiter, waiter)

        # Schedule the function, making sure we release the waiter on finish
        handle = loop.call_later(incr * wait_delta, run_on_finish(release_cb),
                                 say_hi, x)

        # If waiter is cancelled, cancel the handle too.
        waiter.add_done_callback(lambda *args: handle.cancel)

        tasks.append(waiter)

    loop.run_until_complete(asyncio.gather(*tasks))


run()

Keep in mind call_later is used for normal functions and not coroutines. 请记住, call_later用于常规功能而非协程。 If say_hi needs to be a coroutine, you should add ensure_future or loop.create_task into the mix. 如果say_hi需要成为协程,则应在混合中添加ensure_futureloop.create_task

Adding them does come with more complications - you'll need to add a few more functions to take ensure_future 's resulting future, and chain it with your waiter in a similar way to futures._chain_future . 如果把它们也确实会带来更多的并发症-你需要添加更多的功能,从而利用ensure_future的产生的未来,并用类似的方式,以你的服务员IT连锁futures._chain_future

I highly suggest using your own coroutine in that case, as you have already done. 我已经强烈建议您在这种情况下使用自己的协程。

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

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