Python协程(二) Asyncio入门
Asyncio模块提供了使用协程构建并发应用的工具。它使用一种单线程的方式实现并发,一般会在程序阻塞I/O操作的时候发生上下文切换,如读写文件,或者请求网络。
同时Asyncio也支持调度代码在将来的某个特定事件运行,从而支持一个协程等待另一个协程完成,以处理系统信号和识别其他一些事件。
基本概念
Asyncio里面主要以下几个需要关注的基本概念。
1、Eventloop(事件循环)
事件循环是每个 Asyncio 应用的核心。 事件循环会运行异步任务和回调,执行网络 IO 操作,以及运行子进程。
程序开启一个无限循环,并且把一些协程函数注册到这个事件循环上,事件循环会循环执行这些函数 (但同时只能执行一个),当执行到某个函数时,如果它正在等待 I/O 返回,事件循环会暂停它的执行去执行其他的函数;当某个函数完成 I/O 后会恢复,下次循环到它的时候继续执行。因此,这些异步函数可以协同 (Cooperative) 运行:这就是事件循环的目标。
2、可等待对象
如果一个对象可以在 await
语句中使用,那么它就是 可等待 对象。许多 Asyncio API 都被设计为接受可等待对象。
可等待 对象有三种主要类型: 协程, Future 和 任务.
3、协程 (Coroutine)
协程 (Coroutine) 本质上是一个函数,特点是在代码块中可以将执行权交给其他协程。
它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环中,由事件循环调用。
协程通过 async/await 语法进行声明,是编写 Asyncio 应用的推荐方式。 例如,以下代码段(需要 Python 3.7+)会打印 "hello",等待 1 秒,再打印 "world":
>>> import asyncio
>>> async def main():
... print('hello')
... await asyncio.sleep(1)
... print('world')
>>> asyncio.run(main())
hello
world
注意:简单地调用一个协程并不会将其加入执行日程。
要真正运行一个协程,asyncio 提供了三种主要机制:
-
asyncio.run()
函数用来运行最高层级的入口点 "main()" 函数 (参见上面的示例。) -
等待一个协程。以下代码段会在等待 1 秒后打印 "hello",然后 再次 等待 2 秒后打印 "world":
-
import asyncio 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 10:38:33 hello world finished at 10:38:36
asyncio.create_task()
函数用来并发运行作为 asyncio任务
的多个协程。我们修改以上示例,并发 运行两个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')}") # Wait until both tasks are completed (should take # around 2 seconds.) await task1 await task2 print(f"finished at {time.strftime('%X')}") # 运行结果(注意,输出显示代码段的运行时间比之前快了 1 秒:) started at 10:40:58 hello world finished at 10:41:00
4、Future
Future
是一种特殊的 低层级 可等待对象,表示一个异步操作的 最终结果。
当一个 Future 对象 被等待,这意味着协程将保持等待直到该 Future 对象在其他地方操作完毕或取消。
Future 对象通常用来链接 底层回调式代码 和高层异步/等待式代码,在 Asyncio 中需要 Future 对象以便允许通过 async/await 使用基于回调的代码。
事件循环可以监视Future对象是否完成。从而允许应用的一部分等待另一部分完成一些工作。
异步操作结束后会把最终结果设置到这个 Future 对象上。Future 是对协程的封装,通常情况下 没有必要 在应用层级的代码中创建 Future 对象。
Future 对象有时会由库和某些 asyncio API 暴露给用户,用作可等待对象:
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() )
5、Task
Task是Future的一个子类,它用来包装和管理一个协程的执行。任务所需的资源可用时,事件循环会调度任务允许,并生成一个结果,从而可以由其他协程消费。
Task 对象被用来在事件循环中运行协程。如果一个协程在等待一个 Future 对象,Task 对象会挂起该协程的执行并等待该 Future 对象完成。当该 Future 对象 完成,被打包的协程将恢复执行。
事件循环使用协同日程调度: 一个事件循环每次运行一个 Task 对象。而一个 Task 对象会等待一个 Future 对象完成,该事件循环会运行其他 Task、回调或执行 IO 操作。
使用高层级的 asyncio.create_task()
函数来创建 Task 对象,也可用低层级的 loop.create_task()
或 ensure_future()
函数。不建议手动实例化 Task 对象。
要取消一个正在运行的 Task 对象可使用 cancel()
方法。调用此方法将使该 Task 对象抛出一个 CancelledError
异常给打包的协程。如果取消期间一个协程正在等待一个 Future 对象,该 Future 对象也将被取消。
参考文章:
https://zhuanlan.zhihu.com/p/69210021
https://segmentfault.com/q/1010000007863343
https://www.jianshu.com/p/2afbe455b526
https://blog.csdn.net/weixin_45139605/article/details/90798253
https://blog.csdn.net/weixin_41599977/article/details/93656042
https://docs.python.org/zh-cn/3/library/asyncio-eventloop.html#creating-futures-and-tasks
https://python-parallel-programmning-cookbook.readthedocs.io/zh_CN/latest/chapter4/03_Event_loop_management_with_Asyncio.html
https://learnku.com/docs/pymotw/asyncio-asynchronous-io-event-loop-and-concurrency-tools/3423
https://docs.python.org/zh-cn/3/library/asyncio-task.html
http://www.manongjc.com/article/75292.html
https://segmentfault.com/a/1190000012631063
https://www.dongwm.com/post/142/
https://www.jianshu.com/p/71b90a578668
https://realpython.com/async-io-python/#the-event-loop-and-asynciorun