asyncio
asyncio还是一个python的单进程单线程程序,比较适合处理那些需要等待的任务。比如网络通信。
async的核心是一个事件循环event loop。event loop控制着任务的调度运行。同时执行的任务只有一个不存在系统级上下文切换和线程不一样。不存在竞争冒险问题,可以明确知道每个task什么时候停止运算。
事件循环是调度和运行异步任务的机制,事件循环通过生成器实现。生成器允许函数部分执行,在某个特定点停止执行,在再次恢复允许之前维护对象和异常的堆栈。
可等待对象
可等待对象就是可以用在await表达式中的对象,主要有三种可等待对象类型。
- 协程 coroutine object
- 任务 task,用来调度协程
- Future 对象,是一个底层对象,表示异步操作的最终结果。一个 Future 代表一个异步运算的最终结果。线程不安全。当future对象被等待,表示协程将保持等待直到该Future对象在其他地方操作完毕。
coroutine
- coroutine function:由async修饰的函数
async def func():
pass
- coroutine object: 调用coroutine function的返回结果,但不会运行coroutine function里面的代码。
要想运行coroutine代码,需要进入事件循环,然后把coroutine转换成task
asyncio.run(coroutine object)
- asyncio.run接收一个coroutine object作为参数,建立一个事件循环,把coroutine object转换成一个task并注册到建立的事件循环中。事件循环建立后就会寻找可以运行的task,然后运行coroutine代码
- 不能在 asyncio.run 方法内部中再次调用 asyncio.run 方法,此方法在异步代码中只能被调用一次,否则会抛出 RuntimeError 异常;
- asyncio.run 只能执行 async 函数,不能执行普通函数(普通函数需要包装);
- asyncio.run 是阻塞运行的,直到执行的协程完成后才返回
import asyncio
async def coru_f(i):
await asyncio.sleep(1)
return i
async def coroutine_function(index):
print(f"coroutine {index} start")
var = await coru_f(index)
# await把coroutine object转换为一个新的task,并向event loop注册这个task
# 并告诉event loop 需要等待后面的新的task运行完才会继续运行当前task,控制权交还给event loop
# 然后在当前task等待新task运行完成时,event loop 会调度其他task运行
# 当event loop再次调度运行这个task值时,会获取新的task的返回值
# 并将其赋值给var
print(var)
print(f"exit coroutine {index}")
async def main():
await coroutine_function(1)
await coroutine_function(2)
if __name__ == "__main__":
coroutine_obj = main()
# 创建event loop,把coroutine object转换成task,然后运行task
asyncio.run(coroutine_obj) # asyncio.run是串行执行
await
await会挂起当前协程,把控制权交回给event loop
import asyncio
import time
async def cor_f(index, n):
print(f"{index} will sleep {n}s: {time.strftime('%X')}")
await asyncio.sleep(n)
print(f"{index} awaked: {time.strftime('%X')}")
return f"{index} finished: {time.strftime('%X')}"
async def main():
tasks1 = [
asyncio.create_task(cor_f(1, 4), name='task1'),
asyncio.create_task(cor_f(2, 3), name='task2'),
asyncio.create_task(cor_f(3, 5), name='task3')]
for t in tasks1:
print(f"wait {t.get_name()} {time.strftime('%X')}")
await t # 串行执行,挂起main,控制权交给event loop,调度运行其他task
print(f"{t.get_name()} finished: {time.strftime('%X')}")
asyncio.run(main())
# wait task1 22:24:46 # main被挂起,其他task得到运行,main等待task1运行完成
# 1 will sleep 4s: 22:24:46 # 在50s被唤醒
# 2 will sleep 3s: 22:24:46 # 应该在49s task2会被唤醒,
# 3 will sleep 5s: 22:24:46 # 三个task都会被sleep挂起,此时所有除sleep的task都被挂起
# 2 awake: 22:24:49 # task2被唤醒,运行完成
# 1 awake: 22:24:50 # task1被唤醒,运行完成,唤醒main
# task1 finished: 22:24:50 # main继续运行 输出task1完成
# wait task2 22:24:50 # main继续运行,等待task2完成,此时时间为50s,而task2在49秒已经运行完成,所以不会阻塞继续运行main
# task2 finished: 22:24:50 # main继续运行,输出task2完成
# wait task3 22:24:50 # main继续运行,等待task3完成
# 3 awake: 22:24:51 # task3等待了5秒后在51秒被唤醒,task3运行完成,唤醒main
# task3 finished: 22:24:51 # main继续运行,输出task3完成
asyncio.creat_task
asyncio.run和await只能串行执行,asyncio.creat_task同样接收一个coroutine object,会把coroutine object 转换为一个task,返回这个task并且向event loop注册这个task
该任务会在 get_running_loop() 返回的循环中执行,如果当前线程没有在运行的循环则会引发 RuntimeError。
import asyncio
import time
async def cor_fun(delay, string, index):
print(f"task{index} started at {time.strftime('%X')}")
await asyncio.sleep(delay) # 告诉event loop需要等待asyncio.sleep(delay) 运行完成,控制权交回给event loop, event loop就会调度其他task运行
print(string)
print(f"task{index} finished at {time.strftime('%X')}\n")
async def main():
task1 = asyncio.create_task(
cor_fun(1, "hello", 1)
) # 创建task并不会把控制权交回给event loop,所以不会调度运行新建的task,控制权还在main手里会继续运行main这个coroutine function的代码
task2 = asyncio.create_task(
cor_fun(2, "world", 2)
)
print(f"started at {time.strftime('%X')}\n")
await task1 # 告诉event loop需要等待task1 运行完成,控制权交回给event loop, event loop就会调度其他task运行
await task2
print(f"finished at {time.strftime('%X')}\n")
asyncio.run(main())
# started at 23:52:09
# task1 started at 23:52:09
# task2 started at 23:52:09
# hello
# task1 finished at 23:52:10
# world
# task2 finished at 23:52:11
# finished at 23:52:11
asyncio.gather
asyncio.gather接收多个task或coroutine或future,把多个task组合起来,返回一个future对象
import asyncio
import time
from pyrsistent import m
async def sum_(n):
print(f"calculate 1**3+.....+{n}**3 at {time.strftime('%X')}")
await asyncio.sleep(n//3)
ans = sum([i**3 for i in range(1,n+1)])
print(f"finished calculating 1**3+.....+n**3 at {time.strftime('%X')}")
return ans
async def main():
print(f"started at {time.strftime('%X')}")
res = await asyncio.gather(
sum_(5),
sum_(10),
sum_(3)
)
# future对象也可以await,await futer 告诉event loop 需要等待里面每一个task运行完成
# 同时把所有task的返回值放到一个list中返回
print(res)
print(f"finished at {time.strftime('%X')}")
if __name__ == "__main__":
asyncio.run(main())
# started at 00:08:12
# calculate 1**3+.....+n**3 at 00:08:12
# calculate 1**3+.....+n**3 at 00:08:12
# calculate 1**3+.....+n**3 at 00:08:12
# finished calculating 1**3+.....+n**3 at 00:08:13
# finished calculating 1**3+.....+n**3 at 00:08:13
# finished calculating 1**3+.....+n**3 at 00:08:15
# [225, 3025, 36]
# finished at 00:08:15
asyncio.wait_for(aw, timeout)
如果aw是协程将自动被作为任务调度
等待aw(可等待对象)timeour秒或完成,如果超过timeout秒后没有完成,则任务取消,并引发asyncio.TimeoutError。如果等待被取消,aw指定的对象也会被取消。
此函数将等待直到 Future 确实被取消,所以总等待时间可能超过 timeout。 如果在取消期间发生了异常,异常将会被传播。
import asyncio
import time
async def corou_fun():
print(f"enter a coroutine at {time.strftime('%X')}")
await asyncio.sleep(10)
print("hello ")
async def main():
try:
await asyncio.wait_for(corou_fun(), 3) # 等待3秒
except asyncio.TimeoutError as e:
print(f"timeout at {time.strftime('%X')}")
asyncio.run(main())
# enter a coroutine at 21:13:40
# timeout at 21:13:43
asyncio.shield(aw)
保护一个可等待对象防止其被取消,返回一个可等待对象
如果aw是一个协程,它将自动被作为任务调度
res = await asyncio.shield(aw)
# 如果包含aw的协程被取消,aw里的任务不会被取消,但是调用者已被取消,await仍会引发CancelldeError
# 完全忽略取消操作
try:
res = await asyncio.shield(aw)
except CancelledError:
res = None
asyncio.wait(aws, *, timeout=None, return_when=ALL_COMPLETED)
并发的运行aws可迭代对象中的可等待对象并进入阻塞状态直到满足return_when条件,3.8之后不再接收协程
返回两个Task/Future集合
wait() 会自动以任务的形式调度协程,之后将以 (done, pending) 集合形式返回显式创建的任务对象
done, pending = await asyncio.wait(aws)
如指定 timeout (float 或 int 类型) 则它将被用于控制返回之前等待的最长秒数。
此函数不会引发 asyncio.TimeoutError。当超时发生时,未完成的 Future 或 Task 将在指定秒数后被返回。
return_when 指定此函数应在何时返回。它必须为以下常数之一:
- FIRST_COMPLETED 任意可等待对象结束或取消时返回
- FIRST_EXCEPTION 函数将在任意可等待对象因引发异常而结束时返回,没有异常相当于ALL_COMPLETED
- ALL_COMPLETED 函数将在所有可等待对象结束或取消时返回。
import asyncio
async def corou_f(index,n):
print(f"{index} will sleep for {n}s")
await asyncio.sleep(n)
print(f"{index} finished")
async def main():
tasks = [asyncio.create_task(corou_f(1,2),name="task1"),
asyncio.create_task(corou_f(2, 5), name="task2"),
asyncio.create_task(corou_f(3, 1), name="task3")]
done, pending = await asyncio.wait(tasks, timeout=3, return_when=asyncio.ALL_COMPLETED)
# wait超时不会引发TimeoutError
for d in done:
print(d.get_name(), "finished")
for p in pending:
print(p.get_name(), "cancelled")
asyncio.run(main())
# 1 will sleep for 2s
# 2 will sleep for 5s
# 3 will sleep for 1s
# 3 finished
# 1 finished
# task3 finished
# task1 finished
# task2 cancelled
asyncio.as_completed(aws,*,timeout=None)
并发的允许aws可迭代对象中的可迭代对象,返回一个协程的迭代器。按照运行快慢的顺序,先返回最先运行完的协程,然后再返回第二快的
import asyncio
import time
async def cor_f(index, n):
print(f"{index} will sleep {n}s: {time.strftime('%X')}")
await asyncio.sleep(n)
print(f"{index} finished: {time.strftime('%X')}")
return index
async def main():
tasks1 = [
cor_f(1, 3),
cor_f(2, 1),
cor_f(3, 2)
]
i = 1
for obj in asyncio.as_completed(tasks1): # 返回一个协程迭代器,按运行完成顺序
print(f"wait for {i}th cor at: {time.strftime('%X')}")
res = await obj
print(f"task{res} finished: {i}th")
i += 1
asyncio.run(main())
# wait for 1th cor at: 22:51:57 # 等待第一个协程运行完成,挂起main
# 2 will sleep 1s: 22:51:57
# 3 will sleep 2s: 22:51:57
# 1 will sleep 3s: 22:51:57
# 2 finished: 22:51:58 # 第一个运行完成的时task2, 唤醒main
# task2 finished: 1th # main继续运行
# wait for 2th cor at: 22:51:58 # 等待第二个协程完成
# 3 finished: 22:51:59 # 第二个先完成的是task3, 唤醒main
# task3 finished: 2th # main继续运行
# wait for 3th cor at: 22:51:59 # 等待第三个协程完成
# 1 finished: 22:52:00 # 第三个完成的是task1
# task1 finished: 3th
如果在所有 Future 对象完成前发生超时则将引发 asyncio.TimeoutError。
asyncio.to_thread(func, *args,**kwargs)
如果在协程里调用了其他的阻塞io,因为没有await关键字,所以不会挂起当前协程,控制权也不会回到event loop手里,会阻塞整个事件循环。使用to_thread方法在不同线程中异步运行。python3.9的新功能.
- 向此函数提供的任何 *args 和 **kwargs 会被直接传给 func。 并且,当前 contextvars.Context 会被传播,允许在不同的线程中访问来自事件循环的上下文变量。
- 返回一个可被等待以获取 func 的最终结果的协程。
- 这个协程函数主要是用于执行在其他情况下会阻塞事件循环的 IO 密集型函数/方法
import asyncio
import time
def block_io():
print(f"enter a block_io at {time.strftime('%X')}")
time.sleep(3)
print(f"exit block_io at {time.strftime('%X')}")
async def main():
print(f"start at {time.strftime('%X')}")
await asyncio.gather(
asyncio.to_thread(block_io),
asyncio.sleep(2)
)
print(f"finished at {time.strftime('%X')}")
asyncio.run(main())
由于 GIL 的存在,asyncio.to_thread() 通常只能被用来将 IO 密集型函数变为非阻塞的。 但是,对于会释放 GIL 的扩展模块或无此限制的替代性 Python 实现来说,asyncio.to_thread() 也可被用于 CPU 密集型函数。
Task对象
- asyncio.current_task(loop=None) 返回当前运行的task实例,如果没有返回None,loop为None则使用get_running_loop()获取当前事件循环
- asyncio.all_tasks(loop=None) 返回事件循环运行的未完成的Task对象的集合
- asyncio.create_task() 或者loop.create_task()来创建一个task对象
- class asyncio.Task(coro, *, loop=None, name=None)
- task.cancel()请求取消Task对象,会在下次事件循环抛出一个CancelledError异常给被封包的协程。task.cancelled()如果task被取消则返回True
import asyncio
import time
async def cor():
print(f"enter a coroutine at {time.strftime('%X')}")
try:
await asyncio.sleep(30)
except asyncio.CancelledError as e:
print(f"cancelled a coroutine at {time.strftime('%X')}")
raise # 把异常再次抛出
finally:
print("coroutine finished ")
async def main():
task = asyncio.create_task(cor())
await asyncio.sleep(1)
print(f"request to cancel a coroutine at {time.strftime('%X')}")
task.cancel() # 请求取消task,将在下一次事件循环,task运行时发出CancelledError
try:
await task # 挂起main,控制权交还event loop
except asyncio.CancelledError as e:
print(f"cor has been cancelled at {time.strftime('%X')}? {task.cancelled()}")
asyncio.run(main())
# enter a coroutine at 16:40:05
# request to cancel a coroutine at 16:40:06
# cancelled a coroutine at 16:40:06
# coroutine finished
# cor has been cancelled at 16:40:06? True
-
done()
如果 Task 对象 已完成 则返回 True。
当 Task 所封包的协程返回一个值、引发一个异常或 Task 本身被取消时,则会被认为 已完成。 -
result()
返回 Task 的结果。
如果 Task 对象 已完成,其封包的协程的结果会被返回 (或者当协程引发异常时,该异常会被重新引发。)
如果 Task 对象 被取消,此方法会引发一个 CancelledError 异常。
如果 Task 对象的结果还不可用,此方法会引发一个 InvalidStateError 异常。 -
exception()
返回 Task 对象的异常。
如果所封包的协程引发了一个异常,该异常将被返回。如果所封包的协程正常返回则该方法将返回 None
如果 Task 对象 被取消,此方法会引发一个 CancelledError 异常
如果 Task 对象尚未 完成,此方法将引发一个 InvalidStateError 异常。
import asyncio
import time
async def cor():
print(f"enter a coroutine at {time.strftime('%X')}")
await asyncio.sleep(3)
return f"coroutine finished as {time.strftime('%X')}"
async def main():
task = asyncio.create_task(cor())
await task
if task.done():
print(task.result())
asyncio.run(main())
- add_done_callback(callback, *, context=None)
添加一个回调,将在 Task 对象 完成 时被运行。
此方法应该仅在低层级的基于回调的代码中使用。 - remove_done_callback(callback)
从回调列表中移除 callback 。
此方法应该仅在低层级的基于回调的代码中使用。 - get_name()
返回 Task 的名称。
如果没有一个 Task 名称被显式地赋值,默认的 asyncio Task 实现会在实例化期间生成一个默认名称。
3.8 新版功能. - set_name(value)
设置 Task 的名称。
value 参数可以为任意对象,它随后会被转换为字符串。
在默认的 Task 实现中,名称将在任务对象的 repr() 输出中可见。
3.8 新版功能.
import asyncio
import time
import functools
from matplotlib.style import context
async def cor():
print(f"enter a coroutine at {time.strftime('%X')}")
await asyncio.sleep(2)
def callback_func(future, name):
"""理论上回调函数只能接受一个参数,一个Future对象表示异步调用的结果"""
print(f"{name} finished at {time.strftime('%X')}")
async def main():
task = asyncio.create_task(cor())
task.set_name("task_1")
# 可以使用functools.partial偏函数来给回调函数传递其他参数
task.add_done_callback(functools.partial(callback_func, name=task.get_name()))
await asyncio.gather(
task, asyncio.sleep(1)
)
asyncio.run(main())

浙公网安备 33010602011771号