Python之深入理解asyncio(三)

前言

這篇文章是《深入理解asyncio》的第三篇,主要包含回調,多線程和在asyncio中執行同步代碼。

成功回調

可以給Task(Future)添加回調函數,等Task完成后就會自動調用這個(些)回調:


在這里插入圖片描述

可以看到在任務完成后執行了callback函數。我這里順便解釋一個問題,不知道有沒有人注意到。

為什么之前一直推薦大家用 asyncio.create_task,但是很多例子卻用了 loop.create_task?

這是因為在IPython里面支持方便的使用await執行協程,但如果直接用 asyncio.create_task會報「no running event loop」:
在這里插入圖片描述

Eventloop是在單進程里面的單線程中的,在IPython里面await的時候會把協程注冊到一個線程的Eventloop上,但是REPL環境是另外一個線程,不是一個線程,所以會提示這個錯誤,即便 asyncio.events._set_running_loop(loop)設置了loop,任務可以創建倒是不能await:因為task是在線程X的Eventloop上注冊的,但是await時卻到線程Y的Eventloop上去執行。這部分是C實現的,可以看延伸閱讀鏈接1。

所以現在你就會看到很多 loop.create_task的代碼片段,別擔心,在代碼項目里面都是用 asyncio.create_task的,如果你非常想要在IPython里面使用 asyncio.create_task也不是沒有辦法,可以這樣做:
在這里插入圖片描述

這樣就可以啦。我解釋下為什么:

IPython里面能運行await是由于loop_runner函數,這個函數能運行協程(延伸閱讀鏈接2),默認的效果大概是 asyncio.get_event_loop().run_until_complete(coro)。為了讓 asyncio.create_task正常運行我定義了新的loop_runner
通過autoawait這個magic函數就可以重新設置loop_runner
上面的報錯是「no running event loop」,所以通過 events._set_running_loop(loop)設置一個正在運行的loop,但是在默認的loop_runner中也無法運行,會報「Cannot run the event loop while another loop is running」,所以重置await里面那個running的loop,運行結束再設置回去。
如果你覺得有必要,可以在IPython配置文件中設置這個loop_runner到 c.InteractiveShell.loop_runner上~

好,我們說回來, add_done_callback方法也是支持參數的,但是需要用到 functools.partial:
在這里插入圖片描述

調度回調

asyncio提供了3個按需回調的方法,都在Eventloop對象上,而且也支持參數:

call_soon

在下一次事件循環中被回調,回調是按其注冊順序被調用的:
在這里插入圖片描述

這個例子輸出的比較復雜,我挨個分析:

call_soon可以用來設置任務的結果: 用 mark_done
通過2個print可以感受到 call_soon支持參數。
最重要的就是輸出部分了,首先fut.done()的結果是False,因為還沒到下個事件循環,sleep(0)就可以切到下次循環,這樣就會調用三個 call_soon回調,最后再看fut.done()的結果就是True,而且 fut.result()可以拿到之前在 mark_done設置的值了
call_later

安排回調在給定的時間(單位秒)后執行:
在這里插入圖片描述

這次要注意3個回調的延遲時間時間要<=sleep的,要不然還沒來的回調程序就結束了

call_at

安排回調在給定的時間執行,注意這個時間要基于 loop.time() 獲取當前時間
在這里插入圖片描述

同步代碼

前面的代碼都是異步的,就如sleep,需要用 asyncio.sleep而不是阻塞的 time.sleep,如果有同步邏輯,怎么;利用asyncio實現并發呢?答案是用 run_in_executor。在一開始我說過開發者創建 Future 對象情況很少,主要是用 run_in_executor,就是讓同步函數在一個執行器( executor)里面運行:
在這里插入圖片描述

可以看到用 asyncio.gather可以把同步函數邏輯轉化成一個協程,且實現了并發。這里要注意細節,就是函數a是普通函數,不能寫成協程,下面的定義是錯誤的,不能實現并發:
在這里插入圖片描述

因為 a 里面沒有異步代碼,就不要用 asyncdef來定義。需要把這種邏輯用 loop.run_in_executor封裝到協程:
在這里插入圖片描述

在這里插入圖片描述

大家理解了吧?

loop.run_in_executor(None,a)這里面第一個參數是要傳遞 concurrent.futures.Executor實例的,傳遞None會選擇默認的executor:
在這里插入圖片描述

當然我們還可以用進程池,這次換個常用的文件讀寫例子,并且用:

在這里插入圖片描述

多線程

上一個小節用的 run_in_executor就如它方法的名字所示,把協程放到了一個執行器里面,可以在一個線程池,也可以在一個進程池。另外還可以使用 run_coroutine_threadsafe在其他線程執行協程(這是線程安全的):
在這里插入圖片描述

這里面有幾個細節要注意:

協程應該從另一個線程中調用,而非事件循環運行所在線程,所以用 asyncio.new_event_loop()新建一個事件循環
在執行協程前要確保新創建的事件循環是運行著的,所以需要用 start_loop之類的方式啟動循環
接著就可以用 asyncio.run_coroutine_threadsafe執行協程a了,它返回了一個Future對象
可以通過輸出感受到future一開始是pending的,因為協程a里面會sleep 1秒才返回結果
用 future.result(timeout=2)就可以獲得結果,設置timeout的值要大于a協程執行時間,要不然會拋出TimeoutError
一開始我們創建的新的事件循環跑在一個線程里面,由于 loop.run_forever會阻塞程序關閉,所以需要結束時殺掉線程,所以用 call_soon_threadsafe回調函數 shutdown去停止事件循環
這里再說一下 call_soon_threadsafe,看名字就知道它是線程安全版本的 call_soon,其實就是在另外一個線程里面調度回調。BTW, 其實 asyncio.run_coroutine_threadsafe底層也是用的它。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,748評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,165評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,595評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,633評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,435評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,943評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,035評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,175評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,713評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,599評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,788評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,303評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,034評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,412評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,664評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,408評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,747評論 2 370

推薦閱讀更多精彩內容