Await子任务在外部任务完成事件上完成(add_done_callback的异步版本)

时间:2016-06-08 16:50:18

标签: python python-3.x python-asyncio

让我们说我们有一些任务(子任务)应该在外部任务完成时完成。我们无法控制外部任务:我们不知道它何时完成(它可以在子任务完成之前发生),我们不能等待内部的子任务。

在此片段中,我们将收到警告,因为外部任务在子任务之前完成:

import asyncio


def create_sub_task():
    sub_task = asyncio.ensure_future(sub())
    # We want this sub_task to be finished when outer task done


async def sub():
    await asyncio.sleep(2)
    print('sub done')


async def main():  # main is outer task for sub_task
    create_sub_task()
    await asyncio.sleep(1)
    print('outer done')


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

add_done_callback看起来像是一种在外部任务完成时抓住时刻的方法,但是我们不能在这里等待子任务:这个函数是同步的。

我找到的方法是使用事件循环的私有_run_once函数来同步在回调内完成任务:

import asyncio
from functools import partial


def create_sub_task():
    sub_task = asyncio.ensure_future(sub())

    # Callback to wait for sub_task
    outer_task = asyncio.Task.current_task()
    outer_task.add_done_callback(partial(_stop_task, sub_task))


async def sub():
    await asyncio.sleep(2)
    print('sub done')


def _stop_task(sub_task, task):
    # Ugly way to wait sub_task finished:
    loop = asyncio.get_event_loop()
    while not sub_task.done():
        loop._run_once()


async def main():  # main is outer task for sub_task
    create_sub_task()
    await asyncio.sleep(1)
    print('outer done')


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

它有效,但它有许多可能出现问题的丑陋方式。

如何更好地解决任务?

1 个答案:

答案 0 :(得分:1)

AFAIK如果没有内部原因,就无法解决这个问题。我个人会在一个未来asyncio.gather外部和子任务,然后重写回调。

不幸的是Future的回调列表没有公开接口公开(我正在使用_callbacks):

import asyncio

def create_sub_task():
    sub_task = asyncio.ensure_future(sub())
    outer_task = asyncio.Task.current_task()

    multi_fut = asyncio.gather(sub_task, outer_task)
    for cb in outer_task._callbacks:
        multi_fut.add_done_callback(cb)
        outer_task.remove_done_callback(cb)

async def sub():
    await asyncio.sleep(2)
    print('sub done')


async def main():  # main is outer task for sub_task
    create_sub_task()
    await asyncio.sleep(1)
    print('outer done')


if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    loop.run_until_complete(main())

我认为你不想要或者你不能改变流程,但我鼓励你重新思考。也许发布一些上下文 - 约束起源。