目前,我有一个低效的同步生成器,它按顺序发出许多HTTP请求并产生结果。我想使用asyncio
和aiohttp
来并行化请求,从而加快这个生成器的速度,但我希望将它保留为普通生成器(不是PEP 525 async generator),以便调用它的非异步代码不需要修改。我怎样才能创建这样的发电机?
答案 0 :(得分:9)
asyncio.as_completed()
,目前几乎没有记录,采用可迭代的协同程序或期货,并按输入期货完成的顺序返回可迭代的期货。 通常,您可以在await
函数内循环显示其结果和async
成员...
import asyncio
async def first():
await asyncio.sleep(5)
return 'first'
async def second():
await asyncio.sleep(1)
return 'second'
async def third():
await asyncio.sleep(3)
return 'third'
async def main():
for future in asyncio.as_completed([first(), second(), third()]):
print(await future)
loop = asyncio.get_event_loop()
# Prints 'second', then 'third', then 'first'
loop.run_until_complete(main())
...但是出于这个问题的目的,我们想要的是能够从普通的生成器中产生这些结果,这样普通的同步代码就可以使用它们而不必知道async
函数正在在引擎盖下使用。我们可以通过拨打loop.run_until_complete()
来自as_completed
来电... {/ p>所产生的期货来做到这一点
import asyncio
async def first():
await asyncio.sleep(5)
return 'first'
async def second():
await asyncio.sleep(1)
return 'second'
async def third():
await asyncio.sleep(3)
return 'third'
def ordinary_generator():
loop = asyncio.get_event_loop()
for future in asyncio.as_completed([first(), second(), third()]):
yield loop.run_until_complete(future)
# Prints 'second', then 'third', then 'first'
for element in ordinary_generator():
print(element)
通过这种方式,我们将异步代码暴露给非异步地,其方式是不要求调用者将任何函数定义为async
,或者甚至知道{ {1}}正在使用ordinary_generator
。
作为asyncio
的替代实现,在某些情况下提供了更大的灵活性,我们可以使用ordinary_generator()
标记重复调用asyncio.wait()
,而不是循环FIRST_COMPLETED
:
as_completed()
此方法维护import concurrent.futures
def ordinary_generator():
loop = asyncio.get_event_loop()
pending = [first(), second(), third()]
while pending:
done, pending = loop.run_until_complete(
asyncio.wait(
pending,
return_when=concurrent.futures.FIRST_COMPLETED
)
)
for job in done:
yield job.result()
个作业列表,其优点是我们可以对其进行调整,以便即时向pending
列表添加作业。这在我们的异步作业可以向队列添加不可预测数量的其他作业的用例中非常有用 - 例如跟随其访问的每个页面上的所有链接的网络蜘蛛。
答案 1 :(得分:0)
Mark 的回答很棒,但我想贡献一个不同的实现,它不依赖于低级事件循环方法。
关键区别在于,不是执行 yield
,而是提供可用于处理结果的回调:
import asyncio
import random
async def do_stuff():
proc_time = round(random.random(), 2)
print('START: ', proc_time)
await asyncio.sleep(proc_time)
return proc_time
def concurrent_stuff(awaitables, callback):
# Must be async to wait
async def _as_completed():
for coro in asyncio.as_completed(awaitables):
result = await coro
callback(result) # Send result to callback.
# Perform the async calls inside a regular method
asyncio.run(_as_completed())
def when_done(result):
print('FINISHED: ', result)
def main():
awaitables = [do_stuff() for _ in range(5)]
concurrent_stuff(awaitables, when_done)
main()
# START: 0.56
# START: 0.98
# START: 0.39
# START: 0.23
# START: 0.94
# FINISHED: 0.23
# FINISHED: 0.39
# FINISHED: 0.56
# FINISHED: 0.94
# FINISHED: 0.98