无尽的生成器发出的并行异步任务

时间:2018-07-14 19:49:34

标签: python python-asyncio

我有一个非常接近此的代码:

class Parser:
    async def fetch(self, url):
        html = await get(url)
        return html

    @property
    def endless_generator(self):
        while True:
            yield url

    async def loop():
        htmls = []
        for url in self.endless_generator:
            htmls.append(await self.fetch(url))
        return htmls

    def run(self):
        loop = asyncio.get_event_loop()
        try:
            htmls = loop.run_until_complete(self.loop())
        finally:
            loop.close()

parser = Parser()
parser.run()

现在Parser.loop同步运行。

我已经尝试asyncio.waitasyncio.gather来实现Parser.fetch的异步调用,但是我不预先知道URL的数量(因为URL是由无尽的生成器产生的)。

那么,如果事先不知道任务数,如何获得异步调用?

1 个答案:

答案 0 :(得分:2)

  

我已经尝试asyncio.waitasyncio.gather来实现Parser.fetch的异步调用,但是我不预先知道URL的数量(因为URL是由无尽的生成器产生的)。

我认为无限生成器是指事先未知URL数量的生成器,而不是真正的无限生成器(生成无限列表)。这是一个版本,可在URL可用时立即创建任务,并在结果到达时收集结果:

async def loop():
    lp = asyncio.get_event_loop()
    tasks = set()
    result = {}
    any_done = asyncio.Event()

    def _task_done(t):
        tasks.remove(t)
        any_done.set()
        result[t.fetch_url] = t.result()

    for url in self.endless_generator:
        new_task = lp.create_task(self.fetch(url))
        new_task.fetch_url = url
        tasks.add(new_task)
        new_task.add_done_callback(_task_done)
        await any_done.wait()
        any_done.clear()

    while tasks:
        await any_done.wait()
        any_done.clear()

    return result  # mapping url -> html

一个人不能在每次迭代中简单地调用gatherwait,因为那样将等待所有现有任务完成,然后再排队。 wait(return_when=FIRST_COMPLETED)可以工作,但任务数量为O(n**2),因为它每次都会重新设置自己的回调。