iT邦幫忙

2

python的asyncio模組(三):建立Event Loop和定義協程

在上一篇python的asyncio模組(二):異步程式設計基本概念講解完異步程式設計的基本概念之後,就可以進入正式的主題了:

  1. 定義Event Loop
import asyncio
loop = asyncio.get_event_loop() #建立一個Event Loop

# 以下為基本的使用方法
#
# loop.run_until_complete(coroutine)
# coroutine是協程的意思,但其意義在下面會介紹,目前只要把他理解成一個任務就行了
# 這個函數顧名思義,就是讓註冊參數裡的任務並執行,等到任務完成就關閉Event Loop
#
# loop.run_forever()
# 這個函數一執行,Event Loop就會永遠執行不會被關閉,除非在程式中出現loop.stop()就停止
#
  1. 定義協程(coroutin)

先解釋協程的意義,協程可以看做是"能在中途中斷、中途返回值給其他協程、中途恢復、中途傳入參數的函數",和一般的函數只能在起始傳入參數,不能中斷,而且最後返回值給父函數之後就結束的概念不一樣。

定義協程很簡單,只要在定義函數時再前面加入"async"這個關鍵字就行了:

# python3.5
# ubuntu 16.04

import asyncio
loop = asyncio.get_event_loop() #建立一個Event Loop

async def example(): # 定義一個協程
    print("Start example coroutine.")
    # do some process...
    print("Finish example coroutine.")

loop.run_until_complete(example())
# 把example這個coroutine註冊到事件循環裡
# 被註冊進去的事件的定義是loop一啟動就直接觸發的,所以example裡面的任務內容會馬上執行
# output:
# Start example coroutine.
# Finish example coroutine.

但以上的例子無法體會到為何協程可以"中途中斷、中途恢復執行",下個例子我們可以在協程裡加入asyncio.sleep函數讓他中途暫停執行,並將執行權轉到其他的函數,之後在恢復執行:

# python3.5
# ubuntu 16.04

import asyncio
loop = asyncio.get_event_loop() #建立一個Event Loop

async def example1(): # 定義一個中間會被中斷的協程
    print("Start example1 coroutin.")
    await asyncio.sleep(1) # 中斷協程一秒
    print("Finish example1 coroutin.")

async def example2(): # 定義一個協程
    print("Start example2 coroutin.")
    # do some process...
    print("Finish example2 coroutin.")

tasks = [ # 建立一個任務列表
    asyncio.ensure_future(example1()),
    asyncio.ensure_future(example2()),
]

loop.run_until_complete(asyncio.wait(tasks))
# 把example1, example2這兩個coroutin註冊到事件循環裡
# loop啟動,先執行example1,中途暫停example1之後切換到example2,最後再切回example1
# output:
# Start example1 coroutin.
# Start example2 coroutin.
# Finish example2 coroutin.
# Finish example1 coroutin.

從output資訊可以看出example1是有被中斷執行的但上面的範例多了幾個需要解釋的東西:

  1. asyncio.ensure_future(example1())
    這函數把協程對象封裝成一個task對象,這個對象可以儲存任務執行時的狀態與環境,通常大家會說這對象儲存了任務的"上下文"(context),因為有task對象存在,協程才能順利的暫停與恢復執行,因為暫停時的變數內容都存在task對象裡,就像是遊戲存檔一樣。

2020/05/24更新
協程對象本身就能儲存任務的上下文,但經過task對象的包裝才能被Event Loop執行,所以說task對象負責作為Event Loop和協程對象的溝通介面。
協程是任務的實際內容,而task對象的作用是:

  1. 管理這個任務目前處在什麼狀態(ex. pending(等待完成)或是finished(執行完成)或是cancelled(已被取消)),並在為Event Loop和協程對象溝通時做出相應行為
  2. 當協程必須中途暫停時(像example裡面的await asyncio.sleep(1)),task對象會進行結尾會說明的Event:Callback註冊流程,確保異步模式正常運行

其實run_until_complete也是把協程先包裝成task對象後才註冊到Event Loop並執行的。

  1. asyncio.wait(tasks)
    這函數的用處在於把兩個example1和example2的兩個協程對象包成一個大的協程對象,就是把兩個小任務包成一個大任務。

  2. await asyncio.sleep(1)
    asyncio.sleep(1)簡單來說就是啟動一個只有一秒的倒數計時器,比較需要解釋的是await這個關鍵字。

如果沒有加入await的話,那Event Loop只會把計時器放在那邊跑,就不會讓example1暫停了。

如果要讓await這個概念跟上一篇的異步程式概念做一個銜接的話,example1在await之前的部份就像是Callback_B,當遇到await asyncio.sleep(1),Callback_B就會註冊一個"Event_D:Callback_D"進去,而Event_D就是倒數計時器倒數結束,Callback_D就是重新啟動example1在await之後的部份內容。

https://ithelp.ithome.com.tw/upload/images/20181007/20107274shJ5sIoujK.png

下一篇教學:
python的asyncio模組(四):Event loop常用API

參考資料:
不錯的asyncio語法解析
https://www.jianshu.com/p/b5e347b3a17c


尚未有邦友留言

立即登入留言