异步编程:可以看为类似线程但不涉及系统调度,即异步程序可以并发地处理问题但异步程序的上下文在内部不是通过系统调度程序切换。
协程(coroutine):协程: 协程,又称为微线程,是一种比线程更加轻量级的存在,在python的语境中,协程一般是指coroutine function和coroutine object,coroutine function是指有async def定义的函数, 而coroutine function跟普通函数不同,其执行后返回的是一个coroutine object,并不会直接执行。
任务(task):task 对象由coroutine创建,被用来在事件循环中设置日程以便并发执行协程。如果一个coroutine在等待一个furture对象,task 对象会挂起该协程的执行并等待该furture对象完成。当该furture对象完成,被打包的协程将恢复执行。
未来(furture):furture是一种特殊的低层级可等待对象,表示一个异步操作的最终结果。当一个 furture对象被等待,这意味着协程将保持等待直到该furture对象在其他地方操作完毕。在 asyncio 中需要furture对象以便允许通过 async/await 使用基于回调的代码。通常情况下没有必要在应用层级的代码中创建furture对象。
asyncio是python从3.4版本引入的标准版库,用来编写并发代码,使用async/await语法,其被用作多个提供高性能 Python异步框架的基础,包括网络和网站服务,数据库连接库,分布式任务队列等等,该模块往往是构建 IO 密集型和高层级结构化网络代码的最佳选择。
asyncio编程模型就是一个事件循环。其在编程中获取一个EventLoop的引用,然后把需要执行的协程扔到EventLoop中执行,从而实现异步I/O。
asyncio的python文档:asyncio — 异步 I/O(python3.8)
本文使用的asyncio库对应Python版本:python3.7以上
coroutine 由通过 async/await 语法进行声明,是编写 asyncio 应用的推荐方式,async def 声明的为coroutine function,coroutine function执行返回的是coroutine object。
import asyncio
async def main(): # coroutine function
print(0)
if __name__ = '__main__':
main() # run main()
在IDE运行警告
RuntimeWarning: coroutine 'main' was never awaited
main()
RuntimeWarning: Enable tracemalloc to get the object allocation traceback
在终端运行返回值
>>> main()
<coroutine object main at 0x0000011D878B5540>
运行main()函数并未正常执行,而是直接返回coroutine object。
正确运行coroutine function需要结合asyncio进行,使用asyncio运行coroutine有两种方法
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 17:14:32
hello
world
finished at 17:14:34
顺序执行耗时3秒
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')}")
================================================================
# 预期输出
started at 17:14:32
hello
world
finished at 17:14:34
并发执行耗时2秒
tip:一个asyncio.create_task()一次性只能创建一个task
import asyncio
async def factorial(name, number):
f = 1
for i in range(2, number + 1):
print(f"Task {name}: Compute factorial({i})...")
await asyncio.sleep(1)
f *= i
print(f"Task {name}: factorial({number}) = {f}")
async def main():
# Schedule three calls *concurrently*:
await asyncio.gather(
factorial("A", 2),
factorial("B", 3),
factorial("C", 4),
)
asyncio.run(main())
================================================================
# 预期输出
Task A: Compute factorial(2)...
Task B: Compute factorial(2)...
Task C: Compute factorial(2)...
Task A: factorial(2) = 2
Task B: Compute factorial(3)...
Task C: Compute factorial(3)...
Task B: factorial(3) = 6
Task C: Compute factorial(4)...
Task C: factorial(4) = 24
tip:一个asyncio.gather()一次性可以创建多个task
asyncio.gather() 可一次性输入多个coroutine对象并转换为task对象加入事件循环中并发执行,返回一个包含各coroutine返回值的列表,列表元素顺序与输入coroutine对象的顺序一致。asyncio.gather() 函数虽然支持输入多个coroutine,但是不能直接用一个包含多个coroutine的列表传入,因为asyncio.gather() 要求传入参数为awaitable(可等待对象),如果要使用列表形式传入可等待对象,可以采用列表推导的方式进行,将上一个用例进行改写,如下所示:
import asyncio
async def factorial(name, number):
f = 1
for i in range(2, number + 1):
print(f"Task {name}: Compute factorial({i})...")
await asyncio.sleep(1)
f *= i
print(f"Task {name}: factorial({number}) = {f}")
async def main():
# Schedule three calls *concurrently*:
letters = ["A", "B", "C"]
# 列表推到方式将“可等待对象输入”送入到asyncio.gather()函数中
await asyncio.gather(*[factorial(letter, num+2) for num, letter in enumerate(letters)])
asyncio.run(main())
================================================================
# 预期输出
Task A: Compute factorial(2)...
Task B: Compute factorial(2)...
Task C: Compute factorial(2)...
Task A: factorial(2) = 2
Task B: Compute factorial(3)...
Task C: Compute factorial(3)...
Task B: factorial(3) = 6
Task C: Compute factorial(4)...
Task C: factorial(4) = 24
1、使用async def 声明的coroutine必须和await配套使用。