通读Python官方文档之协程、Future与Task

Tasks and coroutines

翻译的python官方文档

这个问题的恶心之处在于,如果你要理解coroutine,你应该理解futuretask。而你如果想理解futuretask你应该先理解coroutine。所以在第一遍阅读官方文档的时候,感觉完全是在梦游。但读到第二遍和第三遍的时候,就清楚很多了。

Coroutines

协程(coroutine)包括两个概念:

  1. 协程函数(async def 或者 @asyncio.coroutine
  2. 协程函数所返回的协程对象。

协程功能:

  1. 通过result = await future或者 result = yeild from future,悬挂协程,直到future完成,获取future的结果/异常(参见下面对futurefuture结果的描述,或等看完future之后回来再阅读这一段)。
  2. 通过 result = await coroutine 或者 result = yeild from coroutine 等待另一个协程的结果(或者异常,异常会被传播)。
  3. returen expression 返回该协程的结果,被await,或者yield from获取。
  4. raise exception,抛出异常,被await,或者yield from获取。

调用协程函数并不能使该协程运行。调用协程函数所返回的协程对象,在被你安排执行之前,不会做任何事情。有两种方式可以启动它:

  1. 通过在一个已经启动的协程中调用:await coroutine或者yield from coroutine
  2. 或者通过ensure_task()以及loop.create_task()安排协程的执行。

只有事件循环在运行的时候,协程才能运行

在本文档中,有些普通函数返回了一个future,也被标记为coroutine。这是故意的,这样以后就可以自由使用这些函数。如果是在回调代码中使用这个函数,用ensure_future包装他。

hello_world.py

import asyncio

#  创建一个协程
async def hello_world():
    print("Hello World!")

loop = asyncio.get_event_loop()
# Blocking call which returns when the hello_world() coroutine is done
# 在事件循环中调用这个协程
# 不过这里只有一个协程,而其不阻塞
loop.run_until_complete(hello_world())
loop.close()

hello_world2.py

# 这段代码和上面的代码执行结果是相同的。只不过用了另一种调用协程的方式
# 先在loop.call_soon()中安排好,再通过loop.run_forever()调用
# 注意,这里在hello_world中,调用了loop.stop(),否则事件循环就不会终止。
import asyncio

def hello_world(loop):
    print('Hello World')
    loop.stop()

loop = asyncio.get_event_loop()

# Schedule a call to hello_world()
loop.call_soon(hello_world, loop)

# Blocking call interrupted by loop.stop()
loop.run_forever()
loop.close()

image

注意这里return 1+2,实际上是raise StopIteration(3)协程其实是在不停返回结果的。最后的结果才会被返回。

future

future是一个容器,或者占位符(placeholder),用于接受异步的结果。这里指的是asyncio.Future而不是coroutines.futures.Future

接口

result()


返回future的结果

set_result()

指示future已结束,并赋值。注意,必须显式地调用这个接口,才能给future赋值。


import asyncio

# 一个对future进行赋值的函数
async def slow_operation(future):
    await asyncio.sleep(1)
    # 给future赋值
    future.set_result('Future is done!')

loop = asyncio.get_event_loop()
# 创建一个future
future1 = asyncio.Future()
# 使用ensure_future 创建Task
asyncio.ensure_future(slow_operation(future1))
future2 = asyncio.Future()
asyncio.ensure_future(slow_operation(future2))
# gather Tasks,并通过run_uniti_complete来启动、终止loop
loop.run_until_complete(asyncio.gather(future1, future2))
print(future1.result())
print(future2.result())
loop.close()

如果我们注释掉`future.set_result('Future is done!')一行,这个程序将永远不会结束。

TASK

Schedule the execution of a coroutine: wrap it in a future. Task is a subclass of Future.

将一个协程的执行过程安排好:用一个future包装起来。TaskFuture的一个子类。

A task is responsible for executing a coroutine object in an event loop. If the wrapped coroutine yields from a future, the task suspends the execution of the wrapped coroutine and waits for the completion of the future. When the future is done, the execution of the wrapped coroutine restarts with the result or the exception of the future.

Task 负责在实现循环中执行一个协程。 如果被包装的协程由一个future产生,task会暂停被包装协程的执行,等待future的完成。当future完成时,被包装协程会重启,当future结果/异常返回。

Event loops use cooperative scheduling: an event loop only runs one task at a time. Other tasks may run in parallel if other event loops are running in different threads. While a task waits for the completion of a future, the event loop executes a new task.

事件循环使用协同调度:事件循环每次只能执行1个操作。其他task可以在别的线程的事件循环中执行。当task等待future完成时,事件循环会执行一个新的task

The cancellation of a task is different from the cancelation of a future. Calling cancel() will throw a CancelledError to the wrapped coroutine. cancelled() only returns True if the wrapped coroutine did not catch the CancelledError exception, or raised a CancelledError exception.

取消task与取消future不同。调用cancel()将会向被包装的协程抛出CacelledError。如果被包装协程没有捕获CacelledError或者抛出CancelledError时, cancelled()才返回True

这里可以参考Task源码中的一段注释

Request that this task cancel itself.
    This arranges for a CancelledError to be thrown into the
    wrapped coroutine on the next cycle through the event loop.
    The coroutine then has a chance to clean up or even deny
    the request using try/except/finally.
    Unlike Future.cancel, this does not guarantee that the
    task will be cancelled: the exception might be caught and
    acted upon, delaying cancellation of the task or preventing
    cancellation completely.  The task may also return a value or
    raise a different exception.
    Immediately after this method is called, Task.cancelled() will
    not return True (unless the task was already cancelled).  A
    task will be marked as cancelled when the wrapped coroutine
    terminates with a CancelledError exception (even if cancel()
    was not called)
    
    

太长了,我就不翻译了大意就是说,虽然taskcancel()函数,只会向被包装协程发出抛出一个异常,但是task是否真的canceled取决于被包装协程如何处理这个异常。

不要直接创建task实例,使用ensure_future()函数或者loop.create_task()方法。

任务相关函数

asyncio.ensure_future

安排协程的执行。用future包装它,返回一个task。

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

将多个协程或future,集成为一个future。
所有的future必须在一个事件循环中。如果所有的future都成功完成了,则按照输入顺序(而不是返回顺序)返回所有result。

asyncio.sleep(delay, result=None, *, loop=None)

sleep函数,注意,是可以返回结果的






一些参考资料
awesome asyncio


线程和协程

参考这篇文章

线程是操作系统层面的“并行”, 协程是应用程序层面的“并行”。

协程本质上就是:提供一个环境,保存一些需要等待的任务,当这些任务可以执行(等待结束)的时候,能够执行。再等待的过程中,程序可以执行别的任务。

以下内容参考自:PYTHON: GENERATORS, COROUTINES, NATIVE COROUTINES AND ASYNC/AWAIT

@asyncio.coroutine
def foo():
    yield from ....
async def foo():
    await ......

注意在@asyncio.coroutine里只能是 yield from, 在async中,只能是await

你可以通过@type.coroutine装饰器,降一个generator变为一个可await得协程。\


Asynchronous Python

多线程:创建多个线程,每个线程处理一个任务。会竞争资源、死锁什么的。CPU负责切换线程、保存恢复context。


Asyncio Documentation

Asnycio的文档,但是感觉写的一般,有些语焉不详。

引用了一片关于线程的文章,还没看

不用gevent的原因,是因为gevent还是使用了线程,而线程是难以调试的。

Some thoughts on asynchronous API design in a post-async/await world

阅读 9.1k

推荐阅读

Sorry, but I have to leave.-Weibo

12 人关注
16 篇文章
专栏主页