Python Async IO - async/await 关键字

发布时间 2023-07-30 15:26:24作者: 可乐芬达

在学习asyncio之前,先理清楚同步/异步的概念:

同步是指完成事务的逻辑,先执行第一个事务,如果阻塞了,会一直等待,直到这个事务完成,再执行第二个事务,顺序执行

异步是和同步相对的,异步是指在处理调用这个事务的之后,不会等待这个事务的处理结果,直接处理第二个事务去了,通过状态、通知、回调来通知调用者处理结果

asyncio函数:
异步IO采用消息循环的模式,重复“读取消息—处理消息”的过程,也就是说异步IO模型”需要一个消息循环,在消息循环中,主线程不断地重复“读取消息-处理消息”这一过程。

event_loop 事件循环:
程序开启一个无限的循环,程序员会把一些函数注册到事件循环上。当满足事件发生的时候,调用相应的协程函数。

coroutine 协程:
协程对象,指一个使用async关键字定义的函数,它的调用不会立即执行函数,而是会返回一个协程对象。协程对象需要注册到事件循环,由事件循环调用。

task 任务:
一个协程对象就是一个原生可以挂起的函数,任务则是对协程进一步封装,其中包含任务的各种状态。

async/await 关键字:
用于定义协程的关键字,async定义一个协程,await用于挂起阻塞的异步调用接口。

asyncio 的运行逻辑

首先我们先看一段代码

import asyncio

async def main():
    print('hello')
    await asyncio.sleep(1)
    print('word')

coro = main()

直接运行的结果如下

sys:1: RuntimeWarning: coroutine 'main' was never awaited

你会发现函数并没有执行,而是返回给我们一个 Warning ,这是由于 async def 定义的 coroutine function 其返回的对象是一个 coroutine object,直接调用并不会直接运行函数内部的代码

如果要运行这个 coroutine function 那就必须完成

  1. 进入 async 模式,也就是 event_loop() 事件循环, 事件循环会安排协同程序的执行
  2. 把 coroutine function 变成一个 task
import asyncio

async def main():
    print('hello')
    await asyncio.sleep(1)
    print('word')

coro = main()
asyncio.run(coro)

结果如下

hello
word

也就是说,coro 这个 coroutine object 会 先通过 asyncio.run 进入 event_loop() 变成第一个 task,同时 event_loop() 开始查找可以运行的 task,然后开始执行 coro 这个 task

多任务运行逻辑

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 14:50:55
hello
world
finished at 14:50:58

在开始理解代码前,首先要了解

  1. asyncio event_loop 中最小的运行单位是 task,当 coroutine 变为 task 时,控制权交还给 event loop 进行调度
  2. 使用await修饰函数,它会返回一个协程(coroutine)对象,如果用来修饰 coroutine,那就只是把控制权交还 event loop

然后开始代码进入 event_loop 后 main 函数变为 task 开始执行,控制权交还 event_loop,main 函数继续执行,执行到第一个 await 他是一个 coroutine 并不是一个 task 所以会先执行第一个 await 再执行第二个 await,总共耗时 3s,如果要同步执行,那就要用到 asyncio.create_task()

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')}")

	task1 = asyncio.create_task(say_after(1, 'hello'))
	task2 = asyncio.create_task(say_after(2, 'world'))

	await task1
	await task2
	print(f"finished at {time.strftime('%X')}")

asyncio.run(main())

结果如下

started at 15:07:05
hello
hello
finished at 15:07:07

如何取返回值

import asyncio
import time

async def say_after(delay, what):
	await asyncio.sleep(delay)
	return f"{what} - {delay}"

async def main():
	print(f"started at {time.strftime('%X')}")

	# method 1
	task1 = asyncio.create_task(say_after(1, 'hello'))
	task2 = asyncio.create_task(say_after(2, 'world'))

	ret1 = await task1
	ret2 = await task2

	# method 2
	ret3 = await asyncio.gather(task1, task2)

	# method 3
	ret4 = await asyncio.gather(
		say_after(1, 'hello'),
		say_after(2, 'world')
	)

	print(f"finished at {time.strftime('%X')}")

asyncio.run(main())