Python asyncio ensure_future 装饰器
Python asyncio ensure_future decorator
假设我是 asyncio 的新手。我正在使用 async/await 来并行化我当前的项目,并且我发现自己将所有协程传递给了 asyncio.ensure_future
。很多这样的东西:
coroutine = my_async_fn(*args, **kwargs)
task = asyncio.ensure_future(coroutine)
我真正想要的是调用异步函数来 return 执行任务而不是空闲协程。我创建了一个装饰器来完成我想要做的事情。
def make_task(fn):
def wrapper(*args, **kwargs):
return asyncio.ensure_future(fn(*args, **kwargs))
return wrapper
@make_task
async def my_async_func(*args, **kwargs):
# usually making a request of some sort
pass
asyncio 是否有内置的方法来执行此操作我一直找不到?如果我一开始就导致这个问题,我是不是用错了 asyncio?
Does asyncio have a built-in way of doing this I haven't been able to
find?
不,asyncio 没有装饰器来将协程函数转换为任务。
Am I using asyncio wrong if I'm lead to this problem to begin with?
如果没有看到你在做什么,很难说,但我认为这可能是真的。虽然创建任务是 asyncio 程序中的常见操作,但我怀疑您是否创建了这么多应该始终是任务的协程。
等待协程 - 是 "call some function asynchronously" 的一种方式,但会阻塞当前执行流直到它完成:
await some()
# you'll reach this line *only* when some() done
另一方面,Task - 是 "run function " 的一种方式,它不会阻塞当前的执行流程:
task = asyncio.ensure_future(some())
# you'll reach this line immediately
当我们编写 asyncio
程序时,我们通常需要第一种方法,因为在开始下一个之前我们通常需要一些操作的结果:
text = await request(url)
links = parse_links(text) # we need to reach this line only when we got 'text'
另一方面,创建任务通常意味着后面的代码不依赖于任务的结果。但同样,它并不总是发生。
自从 ensure_future
returns 之后,一些人立即尝试使用它作为 运行 一些协同程序的方式:
# wrong way to run concurrently:
asyncio.ensure_future(request(url1))
asyncio.ensure_future(request(url2))
asyncio.ensure_future(request(url3))
实现此目的的正确方法是使用 asyncio.gather:
# correct way to run concurrently:
await asyncio.gather(
request(url1),
request(url2),
request(url3),
)
这可能是你想要的?
更新:
我认为在您的案例中使用任务是个好主意。但我认为你不应该使用装饰器:协程功能(发出请求)仍然是一个独立于它的具体使用细节的部分(它将被用作任务)。如果请求同步控制与它们的主要功能是分开的,那么将同步移动到单独的功能中也是有意义的。我会做这样的事情:
import asyncio
async def request(i):
print(f'{i} started')
await asyncio.sleep(i)
print(f'{i} finished')
return i
async def when_ready(conditions, coro_to_start):
await asyncio.gather(*conditions, return_exceptions=True)
return await coro_to_start
async def main():
t = asyncio.ensure_future
t1 = t(request(1))
t2 = t(request(2))
t3 = t(request(3))
t4 = t(when_ready([t1, t2], request(4)))
t5 = t(when_ready([t2, t3], request(5)))
await asyncio.gather(t1, t2, t3, t4, t5)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
finally:
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()
asyncio 在非常早的预发布版本中有 @task
装饰器,但我们删除了它。
原因是装饰器不知道使用什么循环。
asyncio 不会在导入时实例化循环,而且测试套件通常会为每个测试创建一个新循环以进行测试隔离。
假设我是 asyncio 的新手。我正在使用 async/await 来并行化我当前的项目,并且我发现自己将所有协程传递给了 asyncio.ensure_future
。很多这样的东西:
coroutine = my_async_fn(*args, **kwargs)
task = asyncio.ensure_future(coroutine)
我真正想要的是调用异步函数来 return 执行任务而不是空闲协程。我创建了一个装饰器来完成我想要做的事情。
def make_task(fn):
def wrapper(*args, **kwargs):
return asyncio.ensure_future(fn(*args, **kwargs))
return wrapper
@make_task
async def my_async_func(*args, **kwargs):
# usually making a request of some sort
pass
asyncio 是否有内置的方法来执行此操作我一直找不到?如果我一开始就导致这个问题,我是不是用错了 asyncio?
Does asyncio have a built-in way of doing this I haven't been able to find?
不,asyncio 没有装饰器来将协程函数转换为任务。
Am I using asyncio wrong if I'm lead to this problem to begin with?
如果没有看到你在做什么,很难说,但我认为这可能是真的。虽然创建任务是 asyncio 程序中的常见操作,但我怀疑您是否创建了这么多应该始终是任务的协程。
等待协程 - 是 "call some function asynchronously" 的一种方式,但会阻塞当前执行流直到它完成:
await some()
# you'll reach this line *only* when some() done
另一方面,Task - 是 "run function
task = asyncio.ensure_future(some())
# you'll reach this line immediately
当我们编写 asyncio
程序时,我们通常需要第一种方法,因为在开始下一个之前我们通常需要一些操作的结果:
text = await request(url)
links = parse_links(text) # we need to reach this line only when we got 'text'
另一方面,创建任务通常意味着后面的代码不依赖于任务的结果。但同样,它并不总是发生。
自从 ensure_future
returns 之后,一些人立即尝试使用它作为 运行 一些协同程序的方式:
# wrong way to run concurrently:
asyncio.ensure_future(request(url1))
asyncio.ensure_future(request(url2))
asyncio.ensure_future(request(url3))
实现此目的的正确方法是使用 asyncio.gather:
# correct way to run concurrently:
await asyncio.gather(
request(url1),
request(url2),
request(url3),
)
这可能是你想要的?
更新:
我认为在您的案例中使用任务是个好主意。但我认为你不应该使用装饰器:协程功能(发出请求)仍然是一个独立于它的具体使用细节的部分(它将被用作任务)。如果请求同步控制与它们的主要功能是分开的,那么将同步移动到单独的功能中也是有意义的。我会做这样的事情:
import asyncio
async def request(i):
print(f'{i} started')
await asyncio.sleep(i)
print(f'{i} finished')
return i
async def when_ready(conditions, coro_to_start):
await asyncio.gather(*conditions, return_exceptions=True)
return await coro_to_start
async def main():
t = asyncio.ensure_future
t1 = t(request(1))
t2 = t(request(2))
t3 = t(request(3))
t4 = t(when_ready([t1, t2], request(4)))
t5 = t(when_ready([t2, t3], request(5)))
await asyncio.gather(t1, t2, t3, t4, t5)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
try:
loop.run_until_complete(main())
finally:
loop.run_until_complete(loop.shutdown_asyncgens())
loop.close()
asyncio 在非常早的预发布版本中有 @task
装饰器,但我们删除了它。
原因是装饰器不知道使用什么循环。 asyncio 不会在导入时实例化循环,而且测试套件通常会为每个测试创建一个新循环以进行测试隔离。