async — sync — async
話說…不知道是要話說什麼總之看個 code 囧/
import asyncioasync def main():
passif __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
main
是個標準(?)的 async function ,
情境 A 是在 async function 裡面想要把 sync function 改成 async 來呼叫,會像這樣:
def sync_func():
pass # do somethingasync def main():
loop = asyncio.get_event_loop()
rv = await loop.run_in_executor(None, sync_func)
# do other thing
這算是蠻一般的情境, run_in_executor
支援 concurrent.futures 的兩種 Executor,預設(None)是用 ThreadPoolExecutor 。
(沒記錯的話在講 CPU-bound 的文裡面有講到)
情境B 是在 async function 裡面想要呼叫或識別 sync/async function,像是這樣:
async def main(callback):
rv = 1 # do something then got return value
coro = callback(rv)
# coro will be coroutine if callback is async function
if asyncio.iscoroutine(coro):
await coro
這個在 async libraries 裡面蠻常見的,直接支援 sync/async 的呼叫;不過這種判斷忽略了 callback
是 sync function 但回傳 Future
的情況。(可以用 asyncio.isfuture
來檢查)
情境C 是在 sync function 裡面想要呼叫 async function ,像這樣:
async def async_func():
pass # do something asynchronouslydef do_sync():
# problem here
在 do_sync
裡會根據更上一層的情況有不同的解法,
一是不處在 event loop 裡的情況(就是外層沒有任何 asynchronous):
def do_sync(): # for situation C-1
loop = asyncio.get_event_loop()
val = loop.run_until_complete(async_func())
# do other thing
這個其實就是最一開始的範例(?!),沒有 event loop 的話生一個就好惹多簡單(X
二是處在 event loop 裡的情況:
def do_sync(): # for situation C-2
fut = concurrent.futures.Future()
def to_sync():
loop = asyncio.new_event_loop()
val = loop.run_until_complete(async_func())
loop.close()
fut.set_result(val)
threading.Thread(target=to_sync).start()
v = fut.result()
在這種情境下的實際問題是, Python 的 event loop 在運作的時候,每個「執行中」的 function 的 sync
操作都是獨佔的,就像是搶著 GIL 不放那樣;所以在 do_sync
裡面沒辦法把 async function 丟進去等它算完再回來[1],最簡單的解法就是在裡面開另一個 event loop 來執行 async function,然後用 concurrent.futures
的 Future 來傳遞回傳值[2]。
# 雖然我是覺得這種情況應該是設計上的錯誤啦….
[1] 這也是為什麼 async function 得用 await
或是 asyncio.ensure_future
丟到 loop 裡面工作的原因
[2] Thread 執行之後是不會有回傳值的,簡化傳值跟 Lock 之類最簡單的操作就是利用 Future 會 block 的特性
以上 94 今天的廢文,不知道之後其他人有問題的時候會不會找到這篇XDD