Python asyncio模块

async:定义一个协程(coroutine)。【协程函数】调用不会立即执行,而是会返回一个【协程对象】。协程对象需要注册到事件循环,由事件循环调用。
await: 用于挂起阻塞的异步调用接口。
task对象:Future子类,对协程进一步封装,其中包含任务的各种状态,被自动调度执行。
Future对象:低层级可等待对象,表示一个异步操作的最终结果。

运行协程

  • 并发: 不同代码块交替执行的性能,交替执行。——一个 CPU 同时处理多个程序
  • 并行: 不同代码块同时执行的性能,同时执行。——多个CPU同时处理多个程序。
  • 同步:执行 IO 操作时,必须等待执行完成才得到返回结果。
  • 异步:执行 IO 操作时,不必等待执行就能得到返回结果。

要真正运行一个协程,asyncio 提供三种主要机制:

1. asyncio.run()

运行协程: 创建事件循环,运行一个协程,关闭事件循环。

  • 总是会创建一个新事件循环并在结束时关闭。它应当被用作 asyncio 程序的主入口点,理想情况下应当只被调用一次。
  • 当有其他 asyncio 事件循环在同一线程中运行时,此函数不能被调用。
>>> import asyncio

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

>>> asyncio.run(main())
hello
world
源码
def run(main, *, debug=False):
    if events._get_running_loop() is not None:
        raise RuntimeError("asyncio.run() cannot be called from a running event loop")

    if not coroutines.iscoroutine(main):
        raise ValueError("a coroutine was expected, got {!r}".format(main))

    loop = events.new_event_loop()  # 1.创建新事件循环
    try: 
        events.set_event_loop(loop) # 2.set 
        loop.set_debug(debug)
        return loop.run_until_complete(main)
    finally:
        try:
            _cancel_all_tasks(loop)
            loop.run_until_complete(loop.shutdown_asyncgens())
        finally:
            events.set_event_loop(None)
            loop.close()           # 3.关闭

2. await 一个协程:同步执行

await 只能在带有协程中运行。可await的对象有三种主要类型: 协程, 任务 和 Future.

  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())

【共执行3s】执行后,在等待 1秒后打印 hello,然后 再次等待 2 秒后打印 world。预期输出:

started at 17:13:52
hello
world
finished at 17:13:55

3. await 一个任务:并发执行

当一个协程通过 asyncio.create_task() 等函数被封装为一个 任务,该协程会被自动调度执行;如果当前线程没有在运行的循环则会引发 RuntimeError
以下代码先调用asyncio.run创建一个新事件循环。

  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')}")
      # faster 1s than before
      await task1
      await task2
      print(f"finished at {time.strftime('%X')}")
      
  asyncio.run(main())

【共执行2s】执行后:

started at 16:38:31
hello
world
finished at 16:38:33
3.1 create_task

创建task的3种方法: asyncio.create_task vs asyncio.ensure_future vs loop.create_task

  • asyncio.create_task更高阶,底层调用loop.create_task。如果当前线程没有正在运行的事件循环则会引发 RuntimeError。
  • loop.create_task函参是一个协程,但是asyncio.ensure_future除接受协程,还可以是 Future 或 awaitable 对象。
  • 如果当前线程没有在运行的循环则会引发 RuntimeError。
def create_task(coro, *, name=None):
    loop = events.get_running_loop()
    task = loop.create_task(coro)
    _set_task_name(task, name)
    return task

def ensure_future(coro_or_future, *, loop=None):
    if coroutines.iscoroutine(coro_or_future):
        if loop is None:
            loop = events.get_event_loop()
        task = loop.create_task(coro_or_future)
        if task._source_traceback:
            del task._source_traceback[-1]
        return task
    elif futures.isfuture(coro_or_future):
        if loop is not None and loop is not futures._get_loop(coro_or_future):
            raise ValueError('The future belongs to a different loop than the one specified as the loop argument')
        return coro_or_future
    elif inspect.isawaitable(coro_or_future):
        return ensure_future(_wrap_awaitable(coro_or_future), loop=loop)
    else:
        raise TypeError('An asyncio.Future, a coroutine or an awaitable is required')
  • 绑定回调:task执行结束会调用回调函数。
def callback(future):
    print('Callback:', future.result())
    
task.add_done_callback(callback)

其他

event loop

  • policyDefaultEventLoopPolicy()
  • get、set、new
# 返回当前 OS 线程中正在运行的事件循环。
def get_running_loop():
    """ This function is thread-specific."""
    loop = _get_running_loop()
    if loop is None:
        raise RuntimeError('no running event loop')
    return loop
    
# 获取当前事件循环。
def get_event_loop():
    current_loop = _get_running_loop()
    return current_loop or get_event_loop_policy().get_event_loop()
  
# 将 loop 设置为当前OS线程的当前事件循环。
def set_event_loop(loop):
    get_event_loop_policy().set_event_loop(loop)
    
# 创建一个新的事件循环
def new_event_loop():
    return get_event_loop_policy().new_event_loop()
loop.run_until_complete(future)

实际调用loop.run_forever()。返回 Future 结果 或引发相关异常。

  • loop.run_forever()
    • 运行事件循环直到 loop.stop() 被调用。
    • 如果 stop() 在调用 run_forever() 之前被调用,循环将轮询一次 I/O 选择器并设置超时为零,再运行所有已加入计划任务的回调来响应 I/O 事件(以及已加入计划任务的事件),然后退出。
    • 如果 stop() 在 run_forever() 运行期间被调用,循环将运行当前批次的回调然后退出。 请注意在此情况下由回调加入计划任务的新回调将不会运行;它们将会在下次 run_forever() 或 run_until_complete() 被调用时运行。
  • loop.close() vs loop.stop()
    • 关闭时循环必须处于非运行状态。pending状态的回调将被丢弃。此方法清除所有的队列并立即关闭执行器,不会等待执行器完成。
async def do_some_work(x):
    print('Waiting: ', x)
    
# 协程
loop = asyncio.get_event_loop()
loop.run_until_complete(do_some_work(2))

# task: pedding -> finished状态
task = loop.create_task(do_some_work(2))
print(task)
loop = asyncio.get_event_loop()
loop.run_until_complete(task)
print(task)

进阶:阻塞和await

  • await asyncio.sleep() 休眠几秒。
  • await asyncio.gather() 并发执行所有事件的调度和等待。
  • await asyncio.wait() 并发执行事件,阻塞状态直到满足 return_when 所指定的条件。
  • await asyncio.wait_for() 有超时控制的运行。
  • await asyncio.shield() 屏蔽取消操作
区别比较
# await asyncio.gather/wait 并发执行
return_a, return_b = await asyncio.gather(a(), b())  # return_a是协程a的返回值
done, pending = await asyncio.wait([a(), b()])       # task.result()获得协程返回值

asyncio.sleep(delay, result=None, *, loop=None) -> coro   # 休眠5s
  • asyncio.gather vs asyncio.wait
    • asyncio.gather按照输入协程的顺序收集保存协程的返回结果,
    • asyncio.wait的返回值有两项,第一项是完成的任务列表,第二项表示等待完成的任务列表。
    • asyncio.wait和asyncio.gather里面都用asyncio.ensure_future。对于绝大多数场景要并发执行的是协程,所以直接用asyncio.create_task就足够了~
asyncio.gather

asyncio.gather(*aws, loop=None, return_exceptions=False)

  • 并发运行 aws序列中的可等待对象。
    • return_exceptions=False (默认),所引发的首个异常会立即传播给等待 gather() 的任务。aws 序列中的其他可等待对象 不会被取消 并将继续运行。
    • return_exceptions=True,异常会和成功的结果一样处理,并聚合至结果列表。
  • aws序列中的任一对象被取消,它将被当作引发了 CancelledError一样处理。在此情况下 gather() 调用不会被取消,防止一个已提交的对象被取消导致其他对象也被取消。
import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f"Task {name}: Compute factorial({number}), currently i={i}...")
        await asyncio.sleep(1)
        f *= i
    print(f"Task {name}: factorial({number}) = {f}")
    return f

async def main():
    L = await asyncio.gather(
        factorial("A", 2),
        factorial("B", 3),
        factorial("C", 4),
    )
    print(L)

asyncio.run(main())

# Expected output:
#     Task A: Compute factorial(2), currently i=2...
#     Task B: Compute factorial(3), currently i=2...
#     Task C: Compute factorial(4), currently i=2...
#     Task A: factorial(2) = 2
#     Task B: Compute factorial(3), currently i=3...
#     Task C: Compute factorial(4), currently i=3...
#     Task B: factorial(3) = 6
#     Task C: Compute factorial(4), currently i=4...
#     Task C: factorial(4) = 24
#     [2, 6, 24]
asyncio.wait

async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED)
并发运行 。可选参数`return_when’:

  • ALL_COMPLETED'默认会等待全部任务完成;
  • FIRST_COMPLETED:第一个协程完成就返回;
  • FIRST_EXCEPTION:出现第一个异常就返回;
asyncio.sleep

async def sleep(delay, result=None, *, loop=None)
阻塞 delay 指定的秒数。总是会挂起当前任务,以允许其他任务运行。

  • 如果指定参数 result,则当协程完成时将其返回给调用者。
asyncio.shield
res = await shield(something())

事件循环示例

1. loop.run_until_complete(asyncio.wait(tasks))

import asyncio
import time

now = lambda: time.time()

async def do_some_work(x):
    print('Waiting: ', x)
    await asyncio.sleep(x)
    return 'Done after {}s'.format(x)

start = now()

tasks = [
    asyncio.ensure_future(do_some_work(1)),
    asyncio.ensure_future(do_some_work(2)),
    asyncio.ensure_future(do_some_work(4))
]  
loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(asyncio.wait(tasks))
except KeyboardInterrupt as e:
    # 循环task,逐个cancel; 或用 asyncio.gather(*asyncio.Task.all_tasks()).cancel()
    for task in asyncio.Task.all_tasks():   
        print(task.cancel())
    loop.stop()
    loop.run_forever()  # loop stop后还需要再次开启事件循环,最后再close,不然还会抛出异常!!
finally:
    loop.close()

for task in tasks:
    print('Task ret: ', task.result())
print('TIME: ', now() - start)

'''
Waiting:  1
Waiting:  2
Waiting:  4
Task ret:  Done after 1s
Task ret:  Done after 2s
Task ret:  Done after 4s
TIME:  4.003541946411133
'''
# 协程嵌套1: asyncio.wait 将协程列表包装成Task(Future子类)并等待其执行完成
async def main():
    dones, pendings = await asyncio.wait(tasks)
    for task in dones:
        print('Task ret: ', task.result())
 
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
 
# 协程嵌套2: asyncio.gather支持多个协程包装成单个future交给loop
async def main():
    return await asyncio.gather(*tasks)
 
loop = asyncio.get_event_loop()
results = loop.run_until_complete(main())
 
 
# 协程嵌套3: asyncio.as_completed
async def main():
    for task in asyncio.as_completed(tasks):
        result = await task
        print('Task ret: {}'.format(result))

loop = asyncio.get_event_loop()
done = loop.run_until_complete(main())

2. error

  1. new_event_loop: error=Cannot run the event loop while another loop is running

asyncio事件循环不是线程安全的。一个event loop只能在一个线程内调度和执行任务,并且同一时间只有一个任务在运行!

  • 在主线程中调用get_event_loop总能返回属于主线程的event loop对象。
  • 如果处于非主线程中,还需要调用set_event_loop方法指定一个event loop对象,这样get_event_loop才会获取到被标记的event loop对象。
  1. run_until_complete: RuntimeError: This event loop is already running

self._check_running() 如果当前时间循环正在运行,返回RuntimeError


文献:

协程与任务
Python黑魔法 — 异步IO( asyncio) 协程

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值