说明:本文翻译自Coroutines and Tasks,主要介绍asyncio中用于处理协程和任务的方法和接口。在翻译过程中,译者在官方文档的基础上增加了部分样例代码和示意图表,以帮助读者对文档的理解。本文所述内容主要针对Python3.7,对3.7之前的版本有可能不适用,敬请注意。原创内容,转载请注明出处。
译者:马鸣谦
协程
协程(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对象`。
实际上,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秒
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参数指定的协程(coroutine)封装到一个Task中,并调度执行。返回值是一个Task对象。
任务在由get_running_loop()返回的事件循环(loop)中执行。如果当前线程中没有正在运行的事件循环,将会引发RuntimeError异常:
import asyncio
async def coro_1():
print("do somthing")
task = asyncio.create_task(coro_1())
因为当前线程中没有正运行的事件循环,所以引发异常:
Traceback (most recent call last):
File "C:\Program Files\Pyt