本章介绍 asyncio 包,这个包使用事件循环驱动的协程实现并发。
18.1 线程与协程对比
Python 没有提供终止线程的 API,这是有意为之的。若想关闭线程,必须给线程发送消息。
交给 asyncio 处理的协程要使用 @asyncio.coroutine 装饰。这不是强制要求,但是强烈建议这么做。因为这样能在一众普通的函数中把协程凸显出来,也有助于调试:如果还没从中产出值,协程就被垃圾回收了(意味着有操作未完成,因此有可能是个缺陷),那就可以发出警告。这个装饰器不会预激协程。
使用 yield from asyncio.sleep(.1) 代替 time.sleep(.1),这样的休眠不会阻塞事件循环。
asyncio.Task对象差不多与threading.Thread对象等效。Victor Stinner(本章的特约技术审校)指出“,Task 对象像是实现协作式多任务的库(例如 gevent)中的绿色线程(green thread)”。- Task 对象用于驱动协程,Thread 对象用于调用可调用的对象。
- Task 对象不由自己动手实例化,而是通过把协程传给
asyncio.async(...)函数或loop.create_task(...)方法获取。 - 获取的 Task 对象已经排定了运行时间(例如,由 asyncio.async 函数排定);Thread 实例则必须调用 start 方法,明确告知让它运行。
- 在线程版 supervisor 函数中,slow_function 函数是普通的函数,直接由线程调用。在异步版 supervisor 函数中,slow_function 函数是协程,由 yield from 驱动。
- 没有 API 能从外部终止线程,因为线程随时可能被中断,导致系统处于无效状态。如果想终止任务,可以使用 Task.cancel() 实例方法,在协程内部抛出 CancelledError 异常。协程可以在暂停的 yield 处捕获这个异常,处理终止请求。
- supervisor 协程必须在 main 函数中由
loop.run_until_complete方法执行。
18.1.1 asyncio.Future:故意不阻塞
asyncio.Future 类的 .result() 方法没有参数,因此不能指定超时时间。此外,如果调用 .result() 方法时 future 还没运行完毕,那么 .result() 方法不会阻塞去等待结果,而是抛出 asyncio.InvalidStateError 异常。
获取 asyncio.Future 对象的结果通常使用 yield from
使用yield from处理 future,等待 future 运行完毕这一步无需我们关心,而且不会阻塞事 件循环,因为在 asyncio 包中,yield from 的作用是把控制权还给事件循环。
注意,使用yield from处理future与使用add_done_callback方法处理协程的作用一样: 延迟的操作结束后,事件循环不会触发回调对象,而是设置 future 的返回值;而 yield from 表达式则在暂停的协程中生成返回值,恢复执行协程。
总之,因为 asyncio.Future 类的目的是与 yield from 一起使用,所以通常不需要使用以下方法。
- 无需调用
my_future.add_done_callback(...),因为可以直接把想在future运行结束后 执行的操作放在协程中yield from my_future表达式的后面。这是协程的一大优势:协 程是可以暂停和恢复的函数。 - 无需调用
my_future.result(),因为yield from从future中产出的值就是结果(例如,result = yield from my_future)。
18.1.2 从 future、任务和协程中产出
对协程来说,获取 Task 对象有两种主要方式:
asyncio.async(coro_or_future, *, loop=None)
这个函数统一了协程和 future:第一个参数可以是二者中的任何一个。如果是 Future 或 Task 对象,那就原封不动地返回。如果是协程,那么 async 函数会调用 loop.create_ task(...) 方法创建 Task 对象。loop= 关键字参数是可选的,用于传入事件循环;如果 没有传入,那么 async 函数会通过调用 asyncio.get_event_loop() 函数获取循环对象。
BaseEventLoop.create_task(coro)
这个方法排定协程的执行时间,返回一个 asyncio.Task 对象。如果在自定义的 BaseEventLoop 子类上调用,返回的对象可能是外部库(如 Tornado)中与 Task 类兼容的某个类的实例。
