iT邦幫忙

2

python的asyncio模組(四):Event loop常用API

先來複習一下前面的教學:

上上一篇的python的asyncio模組(二):異步程式設計基本概念,我們學到異步程式設計的3個基本概念:

  1. 事件迴圈(Event loop)
  2. 事件(Event)
  3. 回調函數(Callback)

上一篇的python的asyncio模組(三):建立Event Loop和定義協程,我們得知如何定義asyncio程式的兩個最基本的要素:

  1. 事件迴圈(Event loop)
  2. 協程(Coroutine)

其中Coroutine能中途暫停和恢復運作的特性可以很好的實現異步程式設計中的Event與Callback兩個概念,又能避免以前殘害javascript開發者許久的callback hell。

因為當我們要在一個Coroutine裡中途監聽某一個Event發生後再執行後續行為時,只需要用await關鍵字來等待某個Event發生:

def a_coroutine():
   # do something...
    ...
    
    await listen_event_happend()
    
    # do something after event happend...
    ...

而不需要明確定義需要監聽的Event以及對應的Callback,並註冊到Event loop裡,這就很可能會導致callback hell的狀況:

def a_coroutine():
    # do something...
    ...
    
    def do_something_after_event_happend():
        # do something after event happend...
        ...

    register_to_event_loop(listen_event_happend, do_something_after_event_happend)

若上面的例子還聞不出callback hell的味道,可以請讀者想想看以下的例子,要如何改成需要明確定義Event和Callback的寫法:

def a_coroutine():
   # do something...
    ...
    
    await listen_event1_happend()
    # do something after event1 happend...
    ...
    
    await listen_event2_happend()
    # do something after event2 happend...
    ...
    
    await listen_event3_happend()
    # do something after event3 happend...
    ...

因為Coroutine和一般的函數特性有一些本質上的差異,他可以中途暫停等待某個事件發生,所以我們對於一個Coroutine的業務操作,會比一般的函數還要更多,所以asyncio所衍生出來的基本語法就看起來非常的多樣且複雜。

但若掌握了一些基本的方向,對於常用的基本語法,我們還是能逐步的了解這些語法為何被發明出來且如何被使用。

我們必須先了解,當我們定義了一個Coroutine function後,一直到這個function被Event loop執行的過程中,這個function會經歷幾次的對象轉換:

Coroutine function --> Coroutine object --> Task

我們可以從以下例子了解到其中的對象轉換過程:

# python3.5
# ubuntu 16.04

import asyncio

async def func():
    print('func start')
    await asyncio.sleep(1)
    print('func end')

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

print(func) # 原本是一個coroutine function
obj=func()
print(obj) # 執行之後回傳一個coroutine object
task = asyncio.ensure_future(obj)
print(task) # 經過ensure_future包裝成一個Future對象(也可以說是一個Task對象,兩者的差別下一篇教學會說明)

loop.run_until_complete(task) # 把task丟入Event loop執行,並在task結束時結束Event loop

'''
Output:
<function func at 0x7f28e365aa70>
<coroutine object func at 0x7f28e365a9e0>
<Task pending coro=<func() running at test.py:3>>
func start
func end
'''

當我們執行coroutine function之後,並不會真得到執行的結果,事實上function也沒有執行,而是得到一個協程對象(coroutine object)。

然後ensure_future會把coroutine object封裝程一個task對象,在python的asyncio模組(三):建立Event Loop和定義協程就有提到過,這個對象可以儲存任務執行時的狀態與環境,所以coroutine才能暫停與恢復運行。

2020/05/24更新
coroutine object本身就能儲存任務執行時的狀態與環境,而task對象是作為Event loop和coroutine的溝通介面

然後這個Task對象才能真正丟入Event loop下去執行,但就上面的程式來說,我們也可以把coroutine object作為參數丟進loop.run_until_complete,只是在run_until_complete裡也還是會把coroutine object轉成Task對象再去執行。

接下來我們來講一下和Event loop有關的一些常用基本語法:

  1. loop.run_until_complete(future)

這個函數接收一個Task對象,並指定這個Event loop若執行完這個被傳入Task就會停止,不管loop裏面還有沒有其他的Task正在執行當中或是等待執行。

而如同上面所說,這個參數也可以傳入coroutine object,只是在放入Event loop執行前,這個coroutine object會先被轉成Task對象。

  1. loop.run_forever()

顧名思義,這個函數會讓一個loop無止境的運行下去,就算當中所有Task已經被執行完畢只剩下空的loop在運轉,除非在程式中呼叫loop.stop()才能夠停止這個Event loop。

  1. loop.create_task(coro)

這個函數會接收一個coroutine object,並包裝成一個Task對象,同時把這個Task註冊到這個Event loop中等待執行。

而當loop.run_until_complete或loop.run_forever被呼叫時,這些被註冊進去的Task才會被執行。

  1. loop.create_future()

這個函數不接收任何的參數,然後會回傳一個空的Future對象,並註冊到Event loop裏面等待執行。

目前可以視Future對象和Task對象為一樣的東西,兩者定義上的差別下一次會說明。

  1. loop.is_running()

判定一個Event loop是否還在運作。

  1. loop.is_closed()

判定一個Event loop是否已經被close掉。

  1. loop.stop()

stop一個Event loop。

  1. loop.close()

close掉一個Event loop。

下一篇教學:
python的asyncio模組(五):Future對象與Task對象

參考資料:
https://www.jianshu.com/p/b5e347b3a17c
https://www.dongwm.com/post/understand-asyncio-2/
https://juejin.im/post/5c2f12ce6fb9a04a006f2659


尚未有邦友留言

立即登入留言