測試平台系列(96) 如何停止測試任務執行

大家好~我是米洛

我正在從0到1打造一個開源的介面測試平台, 也在編寫一套與之對應的教程,希望大家多多支援。

歡迎關注我的公眾號米洛的測開日記,獲取最新文章教程!

回顧

上一節我們的前置條件支援了python腳本類型,讓我們能夠更靈活處理數據了。

今天我們就玩點好玩的,和大家一起探討:怎麼停止一段python程式碼。之後我們將會運用到pity之中。

知識要點

本文需要大家對asyncio相關知識有一些了解,至於原理方面,大家可以自行查閱,因為我也沒細看。

為什麼要做這個?

針對測試任務執行非常久的時候,看起來會是阻塞的情況,舉個例子:

某個python腳本裡面寫了time.sleep(1000),導致你的case一直好像沒有執行完成,那我們想結束它,該咋整呢?

今天部落客要聊的就是這方面的內容。

先看看”難題”

我們來寫一個簡單的demo。

  • 我們先定義一個非同步無限循環的方法,讓它每隔一秒就列印一行內容
import asyncio


async def run():
    while True:
        print("still alive")
        await asyncio.sleep(1)

試想一下,如果這個方法開啟了,不結束它,它是不是會一直列印下去?沒錯,我們先試試直接運行之:

很顯然,它是絕對不會停止的,除非你關閉這個py程式。

我們把它想像成同步方法,是不是也會遇到這個困難:

import time


def run():
    time.sleep(10000)
    
if __name__ == "__main__":
    run()
    print("done")

可以發現run一旦開始,它就阻塞了整個程式,下面的done必須要等run結束了才能列印出來。

怎麼停止run

我們都知道,在python3.4以後新增了非同步編程相關的概念,最初是由@coroutine這樣的裝飾器放到方法上,把方法標註為非同步方法,後面直接從語言層面支援了非同步方法定義(async),那其實裡面還有很多我們不太常用的部分,比如今天要說的create_task。

  • 我們改寫下方法
import asyncio


async def run():
    while True:
        print("still alive")
        await asyncio.sleep(1)
        
        
async def main():
    await asyncio.create_task(run())


if __name__ == "__main__":
    asyncio.run(main())

這次我們包了一層方法,利用asyncio.create_task來創建非同步任務,create_task接收一個coroutine並執行。

執行之後,可以發現這個和上面的情況,任務也會一直進行下去。

我們繼續下一步改造:

我們去掉await,可以看到run方法確實執行了,但是still alive只列印了一次就結束了。

我對它的理解是,雖然create_task創建了一個非同步任務,但沒說要await,也就是說沒有說要等它結束。

想像一下這些非同步任務都由一個事件循環控制,當你執行main(main本身也是一個非同步任務)的時候,asyncio.run默認是要執行到main方法執行完畢的,也就是說,現在事件循環等待main方法執行完畢,main方法裡面又創建了一個非同步任務,但沒有強調需要該任務完成,創建完畢後,由於main任務已經完成了,就導致整個事件結束了。

梳理一下:

  • 有await

    執行緒開啟 -> 執行main -> main裡面創建非同步任務run -> 等待非同步任務run(一直等一直等)

    由於主執行緒沒有結束,所以整個python程式一直在等待非同步任務執行完畢,畢竟它是死循環,所以會一直等下去。

  • 無await

    執行緒開啟 -> 執行main -> main裡面創建非同步任務run -> 不等待非同步任務run -> main方法結束 -> 執行緒結束 -> 程式退出

    以上都是個人結合go的goroutine給出的理解。肯定會有一些差別的地方。

  • 再次改造

    其實create_task會返回一個task對象,裡面有done和cancel方法,也就是說咱們可以取消他也可以完成他。

import asyncio


async def run():
    while True:
        print("still alive")
        await asyncio.sleep(1)


async def main():
    task = asyncio.create_task(run())
    await asyncio.sleep(2)
    # 2秒後,就停掉這個任務
    task.cancel()
    print("task任務結束了,main也即將完成,整個程式即將退出")


if __name__ == "__main__":
    asyncio.run(main())

一旦調用了create_task,那麼任務就已經開始了,接著我們用await讓main等待2秒,再調用task.cancel方法就可以取消這個task,這樣所有事件都結束,程式也會退出了。

看看gif:

這時候有的同學可能會問了,await2秒以後,不管你是否調用cancel,因為沒有await task,所以程式照樣會退出啊,沒法證明task真的被cancel了

仔細想想,確實說的有道理。那我們再來改造下:

可以看到,這個task死之前還做了垂死掙扎!!!

  • 加上await

可以看到加上await之後,它還是不會停止

那我們加上cancel試試:

很遺憾,報錯了~~~不過沒關係,我們繼續改下。只需要加上異常處理,就可以完美實現2秒後自動取消任務了。

但這個例子依然不是很帥,如果只是想控制非同步任務的執行時間,那我們可以用wait_for:

import asyncio


async def run():
    while True:
        print("still alive")
        await asyncio.sleep(1)


async def main():
    try:
        await asyncio.wait_for(run(), 2)
    except asyncio.TimeoutError:
        print("run方法超過2秒仍未執行完成")


if __name__ == "__main__":
    asyncio.run(main())

這樣2秒後,任務還沒結束也不會繼續執行了。

去掉死循環以後,任務會在2秒內完成(因為只等待1秒),這時候就可以看到timeout異常不會被觸發


emmmm, 今天的內容就介紹到這了,非同步還有挺多玩法的,我也不是很清楚,大家可以互相交流交流~~~