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号
浙公网安备 33010602011771号