asyncio

asyncio还是一个python的单进程单线程程序,比较适合处理那些需要等待的任务。比如网络通信。
async的核心是一个事件循环event loop。event loop控制着任务的调度运行。同时执行的任务只有一个不存在系统级上下文切换和线程不一样。不存在竞争冒险问题,可以明确知道每个task什么时候停止运算。
事件循环是调度和运行异步任务的机制,事件循环通过生成器实现。生成器允许函数部分执行,在某个特定点停止执行,在再次恢复允许之前维护对象和异常的堆栈。

可等待对象

可等待对象就是可以用在await表达式中的对象,主要有三种可等待对象类型。

  1. 协程 coroutine object
  2. 任务 task,用来调度协程
  3. Future 对象,是一个底层对象,表示异步操作的最终结果。一个 Future 代表一个异步运算的最终结果。线程不安全。当future对象被等待,表示协程将保持等待直到该Future对象在其他地方操作完毕。

coroutine

  1. coroutine function:由async修饰的函数
async def func():
    pass
  1. 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 指定此函数应在何时返回。它必须为以下常数之一:

  1. FIRST_COMPLETED 任意可等待对象结束或取消时返回
  2. FIRST_EXCEPTION 函数将在任意可等待对象因引发异常而结束时返回,没有异常相当于ALL_COMPLETED
  3. 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对象

  1. asyncio.current_task(loop=None) 返回当前运行的task实例,如果没有返回None,loop为None则使用get_running_loop()获取当前事件循环
  2. asyncio.all_tasks(loop=None) 返回事件循环运行的未完成的Task对象的集合
  3. asyncio.create_task() 或者loop.create_task()来创建一个task对象
  4. class asyncio.Task(coro, *, loop=None, name=None)
  5. 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

  1. done()
    如果 Task 对象 已完成 则返回 True。
    当 Task 所封包的协程返回一个值、引发一个异常或 Task 本身被取消时,则会被认为 已完成。

  2. result()
    返回 Task 的结果。
    如果 Task 对象 已完成,其封包的协程的结果会被返回 (或者当协程引发异常时,该异常会被重新引发。)
    如果 Task 对象 被取消,此方法会引发一个 CancelledError 异常。
    如果 Task 对象的结果还不可用,此方法会引发一个 InvalidStateError 异常。

  3. 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())
        
  1. add_done_callback(callback, *, context=None)
    添加一个回调,将在 Task 对象 完成 时被运行。
    此方法应该仅在低层级的基于回调的代码中使用。
  2. remove_done_callback(callback)
    从回调列表中移除 callback 。
    此方法应该仅在低层级的基于回调的代码中使用。
  3. get_name()
    返回 Task 的名称。
    如果没有一个 Task 名称被显式地赋值,默认的 asyncio Task 实现会在实例化期间生成一个默认名称。
    3.8 新版功能.
  4. 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())
    
posted @ 2022-06-25 00:12  店里最会撒谎白玉汤  阅读(239)  评论(0)    收藏  举报