摘要:本文翻译自Coroutines and Tasks,主要介绍asyncio中用于处理协程和任务的方法和接口。在翻译过程中,译者在官方文档的基础上增加了部分样例代码和示意图表,以帮助读者对文档的理解。本文内容主要针对python3.7,在低版本的python中可能不适用,敬请留意。原创内容,如需转载请注明出处。
译者:马鸣谦(邮箱:1612557569@qq.com)
协程
协程(coroutines)是通过async/await
定义函数或方法,是使用asyncio进行异步编程的首选途径。如下,是一个协程的例子:
import asyncio
async def main():
print("hello")
await asyncio.sleep(1)
print("world")
上例中的 main
方法就是我们定义的协程
。
我们在交互环境(Python3.7)下执行以上代码,看看效果:
>>> import asyncio
>>> async def main():
... print("hello")
... await asyncio.sleep(1)
... print("world")
>>> asyncio.run(main())
hello
world
需要注意的是:如果像执行普通代码一样直接调用main()
,只会返回一个coroutine
对象,main()
方法内的代码不会执行:
>>> main() #直接执行`main()`返回的是一个`coroutine对象`。
<coroutine object main at 0x0000000002C97848>
实际上,asyncio提供了三种执行协程
的机制:
- 使用
asyncio.run()
执行协程。一般用于执行最顶层的入口函数,如main()
。 await
一个协程
。一般用于在一个协程
中调用另一协程
。 如下是一个示例:
>>> import time
>>> async def say_after(delay,what):
await asyncio.sleep(delay)
print(what)
>>> async def main():
print(f"started at {time.strftime('%X')}")
await say_after(1,"hello")
await say_after(2,"world")
print(f"finished at {time.strftime('%X')}")
>>> asyncio.run(main())
started at 16:47:10
hello
world
finished at 16:47:13
执行耗时 3秒
- 用
asyncio.create_task()
方法将Coroutine(协程)
封装为Task(任务)
。一般用于实现异步并发操作。 需要注意的是,只有在当前线程存在事件循环的时候才能创建任务(Task)。
我们修改以上的例程,并发执行 两个say_after
协程。
async def main():
task1 = asyncio.create_task(say_after(1,"hello"))
task2 = asyncio.create_task(say_after(2,"world"))
print(f"started at {time.strftime('%X')}")
await task1
await task2
print(f"finished at {time.strftime('%X')}")
执行asyncio.run(main())
,结果如下:
started at 17:01:34
hello
world
finished at 17:01:36
耗时2秒
“可等待”对象(Awaitables)
如果一个对象能够被用在await
表达式中,那么我们称这个对象是可等待对象(awaitable object)
。很多asyncio API
都被设计成了可等待的
。
主要有三类可等待对象:
- 协程
coroutine
- 任务
Task
- 未来对象
Future
。
Coroutine(协程)
Python的协程
是可等待的(awaitable)
,因此能够被其他协程
用在await
表达式中。
import asyncio
async def nested():
print("something")
async def main():
# 如果直接调用 "nested()",什么都不会发生.
# 直接调用的时候只是创建了一个 协程对象 ,但这个对象没有被 await,
# 所以它并不会执行.
nested()
# 那么我们 await 这个协程,看看会是什么结果:
await nested() # 将会打印 "something".
asyncio.run(main())
重要:在这篇文章中,术语coroutine
或协程
指代两个关系紧密的概念:
协程函数(coroutine function)
:由async def
定义的函数;协程对象(coroutine object)
:调用协程函数
返回的对象。
asyncio也支持传统的基于生成器的协程。
Task(任务)
Task
用来 并发的 调度协程。
当一个协程
通过类似 asyncio.create_task()
的函数被封装进一个 Task
时,这个协程
会很快被自动调度执行:
import asyncio
async def nested():
return 42
async def main():
# Schedule nested() to run soon concurrently
# with "main()".
task = asyncio.create_task(nested())
# "task" can now be used to cancel "nested()", or
# can simply be awaited to wait until it is complete:
await task
asyncio.run(main())
Future(未来对象)
Future
是一种特殊的 底层 可等待对象,代表一个异步操作的最终结果。
当一个Future
对象被await
的时候,表示当前的协程会持续等待,直到 Future
对象所指向的异步操作执行完毕。
在asyncio中,Future
对象能使基于回调的代码被用于asyn/await
表达式中。
一般情况下,在应用层编程中,没有必要 创建Future
对象。
有时候,有些Future
对象会被一些库和asyncio API暴露出来,我们可以await
它们:
async def main():
await function_that_returns_a_future_object()
# this is also valid:
await asyncio.gather(
function_that_returns_a_future_object(),
some_python_coroutine()
)
底层函数返回Future
对象的一个例子是:loop.run_in_executor
执行asyncio程序
asyncio.run(coro, * , debug=False)
这个函数运行coro
参数指定的 协程
,负责 管理asyncio事件循环 , 终止异步生成器。
在同一个线程中,当已经有asyncio事件循环在执行时,不能调用此函数。
如果debug=True
,事件循环将运行在 调试模式。
此函数总是创建一个新的事件循环,并在最后关闭它。建议将它用作asyncio程序的主入口,并且只调用一次。
Python3.7新增
重要:这个函数是在Python3.7被临时添加到asyncio中的。
创建Task
asyncio.create_task(coro)
将coro
参数指定的