asyncio是Python3引入的異步IO功能乙墙,主要是在語言層面上解決IO阻塞的問題生均。
常規(guī)的用法是在主函數(shù)中運行run_until_complete這個函數(shù)。但是有的時候汉买,代碼中使用的第三方庫也需要在主函數(shù)中運行自己的循環(huán)函數(shù)佩脊,比如PyQt。那么run_until_complete這個函數(shù)就無法調(diào)用了威彰。
我嘗試了下創(chuàng)建一個新的loop對象钧敞,在線程中調(diào)用loop對象的循環(huán)函數(shù)满粗,解決了這個問題调煎。
# 創(chuàng)建一個loop循環(huán)
new_loop = asyncio.new_event_loop()
# 定義線程函數(shù)
def Loop(loop):
? ??while True:
? ??????try:
? ??????????loop.run_forever()? ? # 這里調(diào)用了asynicio的循環(huán)函數(shù)
? ??????except KeyboardInterrupt:? ? # 按ctrl-c能退出
? ??????????loop.close()
# 創(chuàng)建一個線程巡揍,在線程里面執(zhí)行 run_forever
t = threading.Thread(target = Loop, args=(new_loop,))
t.daemon = True
t.start()
while True: # 這里是模擬其他庫的阻塞函數(shù)
? ? pass
在上面的代碼里面耿眉,創(chuàng)建好了異步調(diào)用的流程,接下來是如何調(diào)用不支持asyncio的函數(shù)鸣剪。拿requests來舉例吧丈积。
requests是python下常用的web請求庫债鸡。我們知道http請求是很費時的,調(diào)用一次幾十到幾百毫秒的阻塞唬滑。
現(xiàn)在實現(xiàn)一個異步的請求:
def PostFinishTask(action_id):
? ??asyncio.run_coroutine_threadsafe(PostFinishTaskAsync(action_id), new_loop)
用到的變量是剛才創(chuàng)建的new_loop對象棺弊,表示本次異步調(diào)用會在剛創(chuàng)建的線程里面執(zhí)行,不會阻塞主線程稻艰。
async def PostFinishTaskAsync(action_id):
? ??payload = {?'action_id': action_id }? ? # 模擬的一些網(wǎng)絡(luò)參數(shù)
? ??def Do_post(): # 為了解決傳參的問題侈净,定義一個內(nèi)部函數(shù)
? ??????return requests.post("https://some_call_url", data = payload)
? ??future = new_loop.run_in_executor(None, Do_post)?
????response = await future # 等待調(diào)用完成
這個PostFinishTaskAsync函數(shù)才是真正干活的函數(shù)。首先加上async运怖,然后為了解決run_in_executor無法給它回調(diào)的函數(shù)傳多個參數(shù)拼弃,額外的定義了一個Do_post函數(shù)用來繞過。
經(jīng)過以上改造溺忧,requests變成了非阻塞的調(diào)用了盯孙。
本文解決了兩個問題,一個是如何自定義一個asyncio的loop循環(huán)歌溉。另一個是如何把阻塞函數(shù)改成非阻塞。雖然Python是一個很慢的語言痛垛,但是將IO改造成非阻塞桶蛔,在一定的場合下還是能工作的不錯。