Python协程(asyncio)(二)高级开发

Python实用教程_spiritx的博客-CSDN博客

事件循环

事件循环是每个 asyncio 应用的核心。 事件循环会运行异步任务和回调,执行网络 IO 操作,以及运行子进程。

应用开发者通常应当使用高层级的 asyncio 函数,例如 asyncio.run(),应当很少有必要引用循环对象或调用其方法。 本文学习的内容所针对的主要是低层级代码、库和框架的编写者,他们需要更细致地控制事件循环行为。

事件循环是一种处理多并发量的有效方式,一种等待程序分配事件或消息的编程架构,可以定义事件循环来简化使用轮询方法来监控事件,通俗的说法就是「当A发生时,执行B」。事件循环利用poller对象,使得程序员不用控制任务的添加、删除和事件的控制。事件循环使用回调方法来知道事件的发生。它是asyncio提供的「中央处理设备」,支持如下操作:

  • 注册、执行和取消延迟调用(超时)
  • 创建可用于多种类型的通信的服务端和客户端的Transports
  • 启动进程以及相关的和外部通信程序的Transports
  • 将耗时函数调用委托给一个线程池
  • 单线程(进程)的架构也避免的多线程(进程)修改可变状态的锁的问题。

与事件循环交互的应用要显示地注册将运行的代码,让事件循环在资源可用时向应用代码发出必要的调用。如:一个套接字再没有更多的数据可以读取,那么服务器会把控制全交给事件循环。

asyncio.run()和asyncio.create_task()中使用了默认的事件循环,通常写协程程序不需要显示的使用事件循环,只有在一些需要高级用法的场景,如编写库或者框架时,可能需要显示的使用事件循环。本文就来学习这些高级的用法,在实际应用场景中,需要谨慎使用。

获取事件循环

asyncio.run()和asyncio.create_task()都有默认的事件循环,如果要获取这个默认的事件循环可以通过下面函数获得:

asyncio.get_running_loop()

返回当前 OS 线程中正在运行的事件循环。这个函数仅能被在协程或协程的回调函数中调用,否则会抛出RuntimeError异常。

asyncio.get_event_loop()

获取当前事件循环。在协程或协程的回调函数中被调用时,返回正在运行的事件循环(与asyncio.get_running_loop()是一样的)

如果没有正在运行的事件循环,函数将返回get_event_loop_policy().get_event_loop() 的结果

由于此函数具有相当复杂的行为(特别是在使用了自定义事件循环策略的时候),更推荐在协程和回调中使用 get_running_loop() 函数而非 get_event_loop()。

事件循环处理非常复杂,其行为也难以解释清楚,所以建议使用asyncio.run()函数创建和关闭事件循环。

asyncio.set_event_loop(loop)

设置loop作为当前的事件循环。

asyncio.new_event_loop()

创建并返回一个新的事件循环对象。

注意 get_event_loop(), set_event_loop() 以及 new_event_loop() 函数的行为可以通过 设置自定义事件循环策略 来改变。

停止和运行事件循环

loop.run_until_complete(future)

运行直到 future ( Future 的实例 ) 被完成。
如果参数是 coroutine object ,将被隐式调度为 asyncio.Task 来运行。
返回 Future 的结果 或者引发相关异常。

import asyncio

async def foo():
    print("这是一个协程")


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    try:
        print("开始运行协程")
        coro = foo()
        print("进入事件循环")
        loop.run_until_complete(coro)
    finally:
        print("关闭事件循环")
        loop.close()

这就是最简单的一个协程的例子:
第一步首先得到一个事件循环的应用也就是定义的对象loop。可以使用默认的事件循环,也可以实例化一个特定的循环类(比如uvloop),这里使用了默认循环run_until_complete(coro)方法用这个协程启动循环,协程返回时这个方法将停止循环。
run_until_complete的参数是一个futrue对象。当传入一个协程,其内部会自动封装成task,其中task是Future的子类。关于task和future后面会提到。

上面的协程没有返回值,如果协程要有返回值,可以改写为:

import asyncio

async def foo():
    print("这是一个协程")
    return "返回值"

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    try:
        print("开始运行协程")
        coro = foo()
        print("进入事件循环")
        result = loop.run_until_complete(coro)
        print(f"run_until_complete可以获取协程的{result},默认输出None")
    finally:
        print("关闭事件循环")
        loop.close()

run_until_complete可以获取协程的返回值,如果没有给定返回值,则像函数一样,默认返回None。

loop.run_forever()

运行事件循环直到 stop() 被调用。
如果 stop() 在调用 run_forever() 之前被调用,循环将轮询一次 I/O 选择器并设置超时为零,再运行所有已加入计划任务的回调来响应 I/O 事件(以及已加入计划任务的事件),然后退出。
如果 stop() 在 run_forever() 运行期间被调用,循环将运行当前批次的回调然后退出。 请注意在此情况下由回调加入计划任务的新回调将不会运行;它们将会在下次 run_forever() 或 run_until_complete() 被调用时运行。

其他辅助函数

函数名说明
loop.stop()停止事件循环。
loop.is_running()返回 True 如果事件循环当前正在运行。
loop.is_closed()如果事件循环已经被关闭,返回 True
loop.close()关闭事件循环。当这个函数被调用的时候,循环必须处于非运行状态。pending状态的回调将被丢弃。
此方法清除所有的队列并立即关闭执行器,不会等待执行器完成。
这个方法是幂等的和不可逆的。事件循环关闭后,不应调用其他方法。
coroutine loop.shutdown_asyncgens()安排所有当前打开的 asynchronous generator 对象通过 aclose() 调用来关闭。 在调用此方法后,如果有新的异步生成器被迭代事件循环将会发出警告。 这应当被用来可靠地完成所有已加入计划任务的异步生成器。请注意当使用 asyncio.run() 时不必调用此函数。
coroutine loop.shutdown_default_executor()关闭默认的执行器(ThreadPoolExecutor.)

安排回调

call_soon()

loop.call_soon(callback, *args, context=None)

在下一个迭代的时间循环中立刻调用回调函数。回调按其注册顺序被调用。每个回调仅被调用一次。

import asyncio

def callback_foo(name):
    print(f'callback_foo...{name}')

async def foo():
    # await asyncio.sleep(0.2)
    print(f'async foo...')

async def main(loop):
    print('Start main...')
    loop.call_soon(callback_foo, 'Jackson')
    print('call_soon...')
    await foo()
    print('foo...')
    await asyncio.sleep(0.2)
    print('End main...')

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    try:
        print('Start run_until_complete...')
        loop.run_until_complete(main(loop))
        print('End   run_until_complete...')
    finally:
        loop.close()

‘’'
Start run_until_complete...
Start main...
call_soon...
async foo...
foo...
callback_foo...Jackson
End main...
End   run_until_complete...
‘''
import asyncio

def callback_foo(name):
    print(f'callback_foo...{name}')

async def foo():
    await asyncio.sleep(0.2)
    print(f'async foo...')

async def main(loop):
    print('Start main...')
    loop.call_soon(callback_foo, 'Jackson')
    print('call_soon...')
    await foo()
    print('foo...')
    await asyncio.sleep(0.2)
    print('End main...')

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    try:
        print('Start run_until_complete...')
        loop.run_until_complete(main(loop))
        print('End   run_until_complete...')
    finally:
        loop.close()

‘’'
Start run_until_complete...
Start main...
call_soon...
callback_foo...Jackson
async foo...
foo...
End main...
End   run_until_complete...
‘''

从上面的2个例子可以看出,call_soon注册的回调函数是在事件循环被唤醒后调用的(sleep之后)

loop.call_soon_threadsafe(callback*argscontext=None)

这是一个线程安全的版本。当安排回调函数在另外一个线程执行时,一定要使用这个函数,而不是call_soon()函数。

call_later()

loop.call_later(delay, callback, *args, context=None)​​​​​​​

事件循环在delay多长时间之后才执行callback函数.
配合上面的call_soon让我们看一个小例子

import asyncio


def callback(n):
    print(f"callback {n} invoked")


async def main(loop):
    print("注册callbacks")
    loop.call_later(0.2, callback, 1)
    loop.call_later(0.1, callback, 2)
    loop.call_soon(callback, 3)
    await asyncio.sleep(0.4)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    try:
        loop.run_until_complete(main(loop))
    finally:
        loop.close()

‘’’
注册callbacks
callback 3 invoked
callback 2 invoked
callback 1 invoked
’‘’

通过上面的输出可以得到如下结果:
1.call_soon会在call_later之前执行,和它的位置在哪无关
2.call_later的第一个参数越小,越先执行。

返回一个 asyncio.TimerHandle 对象,该对象能用于取消回调。
callback 只被调用一次。如果两个回调被安排在同样的时间点,执行顺序未限定。

call_at()

loop.call_at(when, callback, *args, context=None)

call_at第一个参数的含义代表的是一个单调时间,它和我们平时说的系统时间有点差异,
这里的时间指的是事件循环内部时间,可以通过loop.time()获取,然后可以在此基础上进行操作。后面的参数和前面的两个方法一样。实际上call_later内部就是调用的call_at。

import asyncio


def call_back(n, loop):
    print(f"callback {n} 运行时间点{loop.time()}")


async def main(loop):
    now = loop.time()
    print("当前的内部时间", now)
    print("循环时间", now)
    print("注册callback")
    loop.call_at(now + 0.1, call_back, 1, loop)
    loop.call_at(now + 0.2, call_back, 2, loop)
    loop.call_soon(call_back, 3, loop)
    await asyncio.sleep(1)


if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    try:
        print("进入事件循环")
        loop.run_until_complete(main(loop))
    finally:
        print("关闭循环")
        loop.close()

‘’’
进入事件循环
当前的内部时间 4412.152849525
循环时间 4412.152849525
注册callback
callback 3 运行时间点4412.152942526
callback 1 运行时间点4412.253202825
callback 2 运行时间点4412.354262512
关闭循环
’‘’

本函数的行为和 call_later() 函数相同,返回一个 asyncio.TimerHandle 对象,该对象能用于取消回调。

time()

loop.time()

根据时间循环内部的单调时钟,返回当前时间为一个 float 值。主要是配合call_at()使用。

Futrue对象

Future表示还没有完成的工作结果。事件循环可以通过监视一个future对象的状态来指示它已经完成。future对象有几个状态:

  • Pending
  • Running
  • Done
  • Cancelled

创建Future的时候,task为pending,事件循环调用执行的时候是running,调用完毕是done,如果需要停止事件循环,就需要先把task取消,状态为cancel。

Future是一个低层级的可等待对象。当一个 Future 对象 被等待,这意味着协程将保持等待直到该 Future 对象在其他地方操作完毕。

class asyncio.Future(*loop=None)

协程可以等待 Future 对象直到它们有结果或设置了异常,或者直到它们被取消。 一个 Future 可被等待多次并且结果相同。
通常 Future 用于支持底层回调式代码(例如在协议实现中使用asyncio transports) 与高层异步/等待式代码交互。
经验告诉我们永远不要面向用户的接口暴露 Future 对象,同时建议使用 loop.create_future() 来创建 Future 对象。这种方法可以让 Future 对象使用其它的事件循环实现,它可以注入自己的优化实现。


主要方法和属性

方法和属性名说明
result()返回 Future 的结果。
如果 Future 状态为 完成 ,并由 set_result() 方法设置一个结果,则返回这个结果。
如果 Future 状态为 完成 ,并由 set_exception() 方法设置一个异常,那么这个方法会引发异常。
如果 Future 已 取消,方法会引发一个 CancelledError 异常。
如果 Future 的结果还不可用,此方法会引发一个 InvalidStateError 异常。
set_result(result)将 Future 标记为 完成 并设置结果。
如果 Future 已经 完成 则抛出一个 InvalidStateError 错误。
set_exception(exception)将 Future 标记为 完成 并设置一个异常。
如果 Future 已经 完成 则抛出一个 InvalidStateError 错误。
done()如果 Future 为已 完成 则返回 True 。
如果 Future 为 取消 或调用 set_result() 设置了结果或调用 set_exception() 设置了异常,那么它就是 完成 。
cancelled()

如果 Future 已 取消 则返回 True
这个方法通常在设置结果或异常前用来检查 Future 是否已 取消 。

if not fut.cancelled():
    fut.set_result(42)

add_done_callback(callback*context=None)

添加一个在 Future 完成 时运行的回调函数。
调用 callback 时,Future 对象是它的唯一参数。
如果调用这个方法时 Future 已经 完成,回调函数会被 loop.call_soon() 调度。
可选键值类的参数 context 允许 callback 运行在一个指定的自定义 contextvars.Context 对象中。如果没有提供 context ,则使用当前上下文。
remove_done_callback(callback)从回调列表中移除 callback 。
返回被移除的回调函数的数量,通常为1,除非一个回调函数被添加多次。
cancel(msg=None)取消 Future 并调度回调函数。
如果 Future 已经 完成 或 取消 ,返回 False 。否则将 Future 状态改为 取消 并在调度回调函数后返回 True 。
exception()返回 Future 已设置的异常。
只有 Future 在 完成 时才返回异常(或者 None ,如果没有设置异常)。
如果 Future 已 取消,方法会引发一个 CancelledError 异常。
如果 Future 还没 完成 ,这个方法会引发一个 InvalidStateError 异常。
get_loop()返回 Future 对象已绑定的事件循环。
import asyncio

async def set_after(fut, delay, value):
    print(f'set_after() = {fut}')
    # Sleep for *delay* seconds.
    await asyncio.sleep(delay)
    print(f'asyncio.sleep() = {fut}')

    # Set *value* as a result of *fut* Future.
    fut.set_result(value)
    print(f'set_result() = {fut}, {value=}')

async def main():
    # Get the current event loop.
    loop = asyncio.get_running_loop()

    # Create a new Future object.
    fut = loop.create_future()
    print(f'loop.create_future() = {fut}')

    # Run "set_after()" coroutine in a parallel Task.
    # We are using the low-level "loop.create_task()" API here because
    # we already have a reference to the event loop at hand.
    # Otherwise we could have just used "asyncio.create_task()".
    loop.create_task(
        set_after(fut, 1, '... world'))

    print('hello ...')

    # Wait until *fut* has a result (1 second) and print it.
    print(await fut)

asyncio.run(main())

‘’'
loop.create_future() = <Future pending>
hello ...
set_after() = <Future pending cb=[Task.task_wakeup()]>
asyncio.sleep() = <Future pending cb=[Task.task_wakeup()]>
set_result() = <Future finished result='... world'>, value='... world'
... world
‘''

前面介绍的Task对象,实际是Futrure的子类

创建Future和Task

loop.create_future()

在事件循环上创建一个future。

这是在asyncio中创建Futures的首选方式。这让第三方事件循环可以提供Future 对象的替代实现(更好的性能或者功能)。

loop.create_task(coro*name=Nonecontext=None)

在事件循环上创建一个任务,返回一个Task对象。第三方的事件循环可以使用它们自己的 Task 子类来满足互操作性。这种情况下结果类型是一个 Task 的子类。

loop.set_task_factory(factory)

设置一个任务工厂,它将由 loop.create_task() 来使用。

如果参数facory是None,缺省的任务工厂将被设置,factory必须是一个可调用对象,参数列表为(loop, coro, context=None),其中loop是被激活的事件循环,coro是协程对象,facory可调用对象返回asyncio.Future对象(或子类对象)

loop.get_task_factory()

返回一个任务工厂,或者如果是使用默认值则返回 None

其他

向指定事件循环提交一个协程

asyncio.run_coroutine_threadsafe(coroloop)

返回一个 concurrent.futures.Future 以等待来自其他 OS 线程的结果。
此函数应该从另一个 OS 线程中调用,而非事件循环运行所在线程。示例:

# Create a coroutine
coro = asyncio.sleep(1, result=3)

# Submit the coroutine to a given loop
future = asyncio.run_coroutine_threadsafe(coro, loop)

# Wait for the result with an optional timeout argument
assert future.result(timeout) == 3

如果在协程内产生了异常,将会通知返回的 Future 对象。它也可被用来取消事件循环中的任务:

try:
    result = future.result(timeout)
except TimeoutError:
    print('The coroutine took too long, cancelling the task...')
    future.cancel()
except Exception as exc:
    print(f'The coroutine raised an exception: {exc!r}')
else:
    print(f'The coroutine returned: {result!r}')

内省

asyncio.current_task(loop=None)
返回当前运行的 Task 实例,如果没有正在运行的任务则返回 None。
如果 loop 为 None 则会使用 get_running_loop() 获取当前事件循环。

asyncio.all_tasks(loop=None)
返回事件循环所运行的未完成的 Task 对象的集合。
如果 loop 为 None,则会使用 get_running_loop() 获取当前事件循环。

asyncio.iscoroutine(obj)
如果 obj 是一个协程对象则返回 True。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值