Python 異步編程筆記:asyncio

我的筆記,不保證正確。html

雖說看到不少人不看好 asyncio,可是這個東西仍是必須學的。。 基於協程的異步,在不少語言中都有,學會了 Python 的,就一通百通。python

1、生成器 generator

Python 的 asyncio 是經過 generator 實現的,要學習 async,先得複習下 generator.shell

1. yield

衆所周知,yield 是用於定義 generator 函數的關鍵字,調用該函數,會返回一個 generatorexpress

>>> def f():
...     yield 1
...     yield 2
... 
>>> f()  # 返回的是 generator
<generator object f at 0x7f672c460570>
>>> g = f()
>>> next(g)  # 經過 next 方法從 generator 獲取值
1
>>> g.__next__()  # next 方法實際是調用了 generator 的 __next__ 方法
2
>>> next(g)  # 生成器運行結束,產生一個 StopIteration 的 exception
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

每次調用 next,generator 都只會運行到下一個 yield 關鍵字所在行,返回 yield 右側的對象,而後暫停在該處,等待下一次 next 調用。編程

從上面的例子看,yield 就是延遲求值而已。**可是 yield 還有一個特性,就是它是一個 expression,有返回值!**看例子:併發

>>> def func():
...     r = yield 1
...     yield r
... 
>>> g = func()
>>> next(g)
1
>>> next(g)  # 經過 next 調用,yield 的返回值爲 None
>>> g2 = func()
>>> next(g2)  # 首先須要經過 next 調用,運行到 yield 語句處
1
>>> g2.send(419)  # 如今用 send 方法,這會將當前所在的 yield 語句的值設置爲你 send 的值,也就是 419
419  # 而後 generator 運行到下一個 yield,返回右邊的值並暫停

generator 有四個實例函數:next、send 是剛剛已經介紹了的,此外還有 throw 用於從 yield 所在處拋出 Exception,和 close 用於關閉 Generator。詳見 Generator-iterator methods異步

2. yield from <iterable>

能夠理解成是 yield <value> from <iterable>,每次調用時它都會從 <iterable> 中取值,直到遇到 StopIteration。纔會從下一個 yield 取值。async

>>> def f():
...     yield from [1, 2, 3, 4]  # iterable
...     yield 5
...     yield from range(4, 0, -1)  # iterable
... 
>>> list(f())
[1, 2, 3, 4, 5, 4, 3, 2, 1]

固然,yield from <iterable> 也是一個 expression,也有值。它的值就是 StopIteration 異常的第一個參數,內置類型的這個值都是 None.ide

>>> def f():
...     r = yield from [1, 2]
...     yield f"value of yield from is {r}"
... 
>>> list(f())
[1, 2, 'value of yield from is None']

當 <iterable> 是 generator 時,yield from 會直接將函數調用委託給這個子 generator,這裏的調用包括了前面說過的 next、send、throw、close 四個函數。 並直接將 sub generator yield 的值 yield 給 caller.異步編程

3. yield 和 return 混用會發生什麼?

generator 中的 return value,語義上等同於 rasie StopIteration(value)

>>> def f():
...     yield 1
...     return 2
...     yield 3  # 永遠不會被執行
... 
>>> g = f()
>>> next(g)
1
>>> next(g)  # return 引起 StopIteration
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration: 2
>>> next(g)  # 再次調用,StopIteration 變成無參了。
Traceback (most recent call last):
  File "<input>", line 1, in <module>
StopIteration

能夠看到 return 引起了 StopIteration 異常,而 return 的值則成了該異常的第一個參數。

以前說過 yield from <sub generator> 表達式的值,就是該 <sub generator> 的 StopIteration 異常的第一個參數,所以:

>>> def f2():
...     a = yield from f()
...     yield a  # a 是 f() 中 return 的值
...     
>>> list(f2())
[1, 2]

PEP 479 -- Change StopIteration handling inside generators 修改了StopIteration 的行爲,該 PEP 令人爲 raise 的 StopIteration 引起一個 RuntimeError。 該 PEP 在 Python 3.5 版本添加到 future 中,並在 Python 3.7 成爲默認行爲。 所以除非你確實想要引起異常,不然應該使用 return 來結束一個 generator 並返回值。

2、異步IO、協程與非阻塞 IO

先了解一下 進程線程協程與併發並行各類 IO 模型

3、asyncio 的簡單使用

asyncio 引入了兩個新關鍵字:async 和 await,其中 async 能放在三個地方:

  1. async def:用於定義異步函數和異步生成器
    • 不含有 yield 的是 async def 定義的是協程函數(coroutine function),調用該函數返回協程對象(coroutine object),協程對象須要經過 EventLoop 運行。
    • 內部含有 yield 的 async def 定義的是異步生成器函數(asynchronous generator function),調用該函數返回異步生成器(async_generator)
      • 異步生成器只能用在 Coroutine 中
    • async def 中不容許使用 yield from
  2. async for:表示 for 迭代的是一個異步生成器,該 for 循環的每一次迭代,都是異步的。
    • 只能用在 async def 的內部
  3. async with:表示 with 管理的是一個異步上下文管理器(asynchronous context manager)
    • 該 context manager 的 enter 和 exit 兩個步驟是異步的
    • 只能用在 async def 的內部

注意異步 generator、context manager,它的 protocol 都和同步的不一樣,不能混爲一談。 具體而言,對同步 protocol xxx 函數,它的異步版本爲 axxx,就是加個 a。

而 await,就至關於 yield from,差異在於 await 是異步的。還有咱們關心的是 await 表達式的值,而 yield from 中咱們更關心它向上層 yield 的值。

在 yield from 中,當前生成器調用另外一個生成器,當前生成器會掛起,直到另外一個生成器返回。

可是在 await 中,當前 Coroutine 掛起時, eventloop 會尋找其餘 task 來跑,這就利用上了 IO 漫長的等待時間。

async for 是每次迭代都會 await 一次,若是迭代對象是 IO 操做,這個 IO 等待時間就會被利用上。

async with 也是一樣,若是 context 的 enter 和 exit 是 IO 操做,這個 IO 時間就會被 eventloop 用於運行其餘 task.

使用 asyncio 時,咱們要用 async def 將全部的 IO 操做都定義成異步操做。而後在調用時,都使用 await/async for/async with 來調用。

Coroutine、Task 和 Future

首先,每一個協程對象,都是一個獨立的協程單元,協程對象之間能夠異步運行。

協程須要放到 EventLoop 內運行,要運行一個協程 a,有三種方法:

  1. 經過 asyncio.run(coro) 運行一個協程。
    • 該方法會新建一個 EventLoop
  2. 在另外一個協程 b 中經過 await 調用 a。當 b 運行時, a 也會被 task 運行。
  3. 經過 asyncio.create_task(coro),將須要運行的協程包裝成 task,而後經過 task 相關的方法來異步運行它們。
    • asyncio.gather(*awaitable_objects): 併發執行全部的 task,阻塞到全部 task 結束。返回一個 result 列表。result 的列表順序和 future 的順序一致
    • asyncio.as_completed(aws, *, loop=None, timeout=None),和 gather 的區別在於,它返回一個異步迭代器,每次迭代都返回最早完成的一個 future.

concurrent.futures 是進程線程的異步執行,而 asyncio 是基於協程的單線程異步執行

參考

相關文章
相關標籤/搜索