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关联,从而使函数能够返回有意义的值。