通讀Python官方文檔之協程、Future與Task

Tasks and coroutines

翻譯的python官方文檔html

這個問題的噁心之處在於,若是你要理解coroutine,你應該理解futuretask。而你若是想理解futuretask你應該先理解coroutine。因此在第一遍閱讀官方文檔的時候,感受徹底是在夢遊。但讀到第二遍和第三遍的時候,就清楚不少了。python

Coroutines

協程(coroutine)包括兩個概念:git

  1. 協程函數(async def 或者 @asyncio.coroutine
  2. 協程函數所返回的協程對象。

協程功能:github

  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獲取。

調用協程函數並不能使該協程運行。調用協程函數所返回的協程對象,在被你安排執行以前,不會作任何事情。有兩種方式能夠啓動它:express

  1. 經過在一個已經啓動的協程中調用:await coroutine或者yield from coroutine
  2. 或者經過ensure_task()以及loop.create_task()安排協程的執行。

只有事件循環在運行的時候,協程才能運行api

在本文檔中,有些普通函數返回了一個 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.pyapp

# 這段代碼和上面的代碼執行結果是相同的。只不過用了另外一種調用協程的方式
# 先在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)協程實際上是在不停返回結果的。最後的結果纔會被返回。less

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

相關文章
相關標籤/搜索