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

Python 无尽生成器发出的并行异步任务,python,python-asyncio,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 = []

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

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.wait
asyncio.gather
来实现
Parser.fetch
的异步调用,但我事先不知道URL的数量(因为URL由无尽的生成器生成)

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

我尝试了
asyncio.wait
asyncio.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
不能在每次迭代中简单地调用
gather
wait
,因为这将等待所有现有任务完成,然后再对新任务进行排队
wait(return\u when=FIRST\u COMPLETED)
可以工作,但是在任务数量上它将是
O(n**2)
,因为它每次都会重新设置自己的回调

我尝试了
asyncio.wait
asyncio.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

不能在每次迭代中简单地调用
gather
wait
,因为这将等待所有现有任务完成,然后再对新任务进行排队
wait(return\u when=FIRST\u COMPLETED)
可以工作,但在任务数量上它将是
O(n**2)
,因为它每次都会重新设置自己的回调。

tasks=[self.fetch(url)for url in self.uncil\u生成器]
在内存中生成一个列表。首先,在我的例子中效率很低(URL的数量可能很大)。次要的,
self.uncentral\u generator
生成URL
,而某些\u标志为True
,我在另一个方法中更改了它的状态,因此generator是“近似无止境的”。@0x1337我现在编辑了答案,以满足您的要求,但我仍然不能100%确定。也许
无止境的\u生成器
真的应该是一个异步生成器,这样整个任务才能正常工作。
新任务的作用是什么。获取\u url
?@0x1337缺少分配,对此表示抱歉。它将任务与url相关联,从而使函数能够返回有意义的值。
tasks=[self.fetch(url)for url in self.unlimited\u generator]
在内存中生成一个列表。首先,在我的例子中效率很低(URL的数量可能很大)。次要的,
self.uncentral\u generator
生成URL
,而某些\u标志为True
,我在另一个方法中更改了它的状态,因此generator是“近似无止境的”。@0x1337我现在编辑了答案,以满足您的要求,但我仍然不能100%确定。也许
无止境的\u生成器
真的应该是一个异步生成器,这样整个任务才能正常工作。
新任务的作用是什么。获取\u url
?@0x1337缺少分配,对此表示抱歉。它将任务与url关联,从而使函数能够返回有意义的值。