事件循环
事件循环是每个 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, *args, context=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 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=None, context=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(coro, loop)
返回一个 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。