guest@blog.cmj.tw: ~/posts $

Python async


[TBC]

Python 3.5 加入了 async def 的語法支援:用來產生 coroutine 函數、而其中可以再使用 awaitasync forasync with 語法 (這些語法也僅能在 coroutine 函數內使用)。 Python 3.7 之後 asyncawait 則變成保留字。一個最簡單的 coroutine 如下:

# define a coroutine function
async def foo():
	return 'ping'

if __name__ == '__main__':
	# get the coroutine
	f = foo()

	# >>> <class 'coroutine'>
	print(type(f))

	try:
		# method of builtins.coroutine instance
		# return next iterated value or raise StopIteration.
		f.send(None)
	except StopIteration as e:
		# >>> ping
		print(e.value)

在上面的函數 foo 中只回傳了一個字串,因此透過 built-in 函數 send 就不用送額外的參數進去。因為在 generator 的角度來看這個 coroutine 已經結束,因此使用 StopIteration 將結果回傳。

如果需要等待一個 coroutine 的結果,就可以使用 await 保留字:在 coroutine 中如果再呼叫另一個 coroutine 則需要使用 await 保留字,如果沒有就跟直接呼叫 coroutine 一樣出現 RuntimeError 。一個簡單的 await 可以使用 兩個 coroutine 組合而成:

async def x():
	return 'x'

async def y():
	try:
		await x().send(None)
	except StopIteration as e:
		return f'{e} -> y'

try:
	y().send(None)
except StopIteration as e:
	print(f'finish {e}')

await 可以使用 asyncio.sleep(1) 來將目前的 coroutine 暫停,並等待一秒的時間。當使用 asyncio.sleep(0) 的時候等價於直接 yield 一個空的 coroutine,除此之外則是使用 create_future 產生一個新的 Future 並等待指定秒數直到結束。

Coroutine / Task / Future

在使用 coroutine 的時候同時會了解兩個新的名詞: Task Future 。對於 Task 是屬於 asyncio 裡的一個 class 實作, 按照描述是一個用來執行 coroutine、 Future-Like 的物件並且非 thread-safe :如果 coroutine 在 Future 內觸發 await 就會暫停 (suspends)、等到 Future 狀態改為 done 之後才會恢復 (resumes) 執行狀態。

而 Future 則代表一個 aync 的執行結果 (同時也為非 thread-safe)。一個 Future 同時也是一個 awaitable 物件,代表一個 coroutine 可以等待直到 1) 回傳結果 2) 拋出例外 或者 3) 取消。

types.coroutine

在早期的 code 中可以使用 types.coroutine 這個函數將 generator 轉換成 coroutine (如果已經是 coroutine 則直接回傳) 。作法在 程式碼 中可以看到是透過底層 C 實作替換、否則就透過透過 functools.wraps 產生 coroutine (但是可能最後不是 coroutine)。在下面的程式就是透過 types.coroutine 將一個 generator 轉換成 coroutine:

import types

@types.coroutine
def inner():
	yield 1
	return 2

async def outer():
	print(await inner())

fn = outer()
# >>> 1, from yield 1
fn.send(None)
# >>> 2, from return 2
fn.send(None)
# >>> StopIteration
fn.send(None)

async-generator

同理,可以在 async 裡面使用 yield 保留字而非 return 來產生一個 async_generator:根據 PEP 525 的描述, 這是一個 generator 的非同步實作。

async-for

在 coroutine 函數中使用 async-for 語法,代表在 iter 部份中被非同步呼叫。語法等價於將 iter 使用 await 語法呼叫。

async-with

async-for,使用 async-with 表示在 with-statement 中的語法使用非同步方式呼叫。