文章目录
- 运行协程
- 1. asyncio.run()
- 源码
- 2. await 一个协程:同步执行
- 3. await 一个任务:并发执行
- 3.1 create_task
- 其他
- event loop
- loop.run_until_complete(future)
- 进阶:阻塞和await
- 区别比较
- asyncio.gather
- asyncio.wait
- asyncio.sleep
- asyncio.shield
- 事件循环示例
- 1. loop.run_until_complete(asyncio.wait(tasks))
- 2. error
- 文献:
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
vsasyncio.ensure_future
vsloop.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
-
policy
:DefaultEventLoopPolicy()
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
vsasyncio.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
- 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对象。
- run_until_complete:
RuntimeError: This event loop is already running
self._check_running()
如果当前时间循环正在运行,返回RuntimeError