Python 中的 async await 概念


前言

寫這篇文章是受 xinghun85 的這篇博客 的啟發, 但是人家后面寫的東西跳躍太快, 有點沒看懂, 自己在此做一個補充.

我希望能用一個最平易近人的例子, 把 Python 協程中的 async/await 概念講清楚, 希望能夠幫助大家有一個形象化的認識.


注: 所有的講解都在代碼的注釋里.

from time import sleep, time


def demo1():
    """ 假設我們有三台洗衣機, 現在有三批衣服需要分別放到這三台洗衣機里面洗. """
    
    def washing1():
        sleep(3)  # 第一台洗衣機, 需要洗3秒才能洗完 (只是打個比方)
        print('washer1 finished')  # 洗完的時候, 洗衣機會響一下, 告訴我們洗完了
    
    def washing2():
        sleep(2)
        print('washer2 finished')
    
    def washing3():
        sleep(5)
        print('washer3 finished')
    
    washing1()
    washing2()
    washing3()
    
    """ 這個還是很容易理解的, 運行 demo1(), 那么需要10秒鍾才能把全部衣服洗完. 沒錯, 大部分時間都花在挨個地等洗衣機上了. """


def demo2():
    """ 現在我們想要避免無謂的等待, 為了提高效率, 我們將使用 async. washing1/2/3() 本是 "普通函數", 現在我們用 async 把它們升級為 "異步函數". 注: 一個異步的函數, 有個更標准的稱呼, 我們叫它 "協程" (coroutine). """
    
    async def washing1():
        sleep(3)
        print('washer1 finished')
    
    async def washing2():
        sleep(2)
        print('washer2 finished')
    
    async def washing3():
        sleep(5)
        print('washer3 finished')
    
    washing1()
    washing2()
    washing3()
    
    """ 從正常人的理解來看, 我們現在有了異步函數, 但是卻忘了定義應該什么時候 "離開" 一台洗衣 機, 去看看另一個... 這就會導致, 現在的情況是我們一邊看着第一台洗衣機, 一邊着急地想着 "是不是該去開第二台洗衣機了呢?" 但又不敢去 (只是打個比方), 最終還是花了10秒的時間才 把衣服洗完. PS: 其實 demo2() 是無法運行的, Python 會直接警告你: RuntimeWarning: coroutine 'demo2.<locals>.washing1' was never awaited RuntimeWarning: coroutine 'demo2.<locals>.washing2' was never awaited RuntimeWarning: coroutine 'demo2.<locals>.washing3' was never awaited """


def demo3():
    """ 現在我們吸取了上次的教訓, 告訴自己洗衣服的過程是 "可等待的" (awaitable), 在它開始洗衣服 的時候, 我們可以去弄別的機器. """
    
    async def washing1():
        await sleep(3)  # 注意這里加入了 await
        print('washer1 finished')
    
    async def washing2():
        await sleep(2)
        print('washer2 finished')
    
    async def washing3():
        await sleep(5)
        print('washer3 finished')
    
    washing1()
    washing2()
    washing3()
    
    """ 嘗試運行一下, 我們會發現還是會報錯 (報錯內容和 demo2 一樣). 這里我說一下原因, 以及在 demo4 中會給出一個最終答案: 1. 第一個問題是, await 后面必須跟一個 awaitable 類型或者具有 __await__ 屬性的 對象. 這個 awaitable, 並不是我們認為 sleep() 是 awaitable 就可以 await 了, 常見的 awaitable 對象應該是: await asyncio.sleep(3) # asyncio 庫的 sleep() 機制與 time.sleep() 不 # 同, 前者是 "假性睡眠", 后者是會導致線程阻塞的 "真性睡眠" await an_async_function() # 一個異步的函數, 也是可等待的對象 以下是不可等待的: await time.sleep(3) x = await 'hello' # <class 'str'> doesn't define '__await__' x = await 3 + 2 # <class 'int'> dosen't define '__await__' x = await None # ... x = await a_sync_function() # 普通的函數, 是不可等待的 2. 第二個問題是, 如果我們要執行異步函數, 不能用這樣的調用方法: washing1() washing2() washing3() 而應該用 asyncio 庫中的事件循環機制來啟動 (具體見 demo4 講解). """


def demo4():
    """ 這是最終我們想要的實現. """
    import asyncio  # 引入 asyncio 庫
    
    async def washing1():
        await asyncio.sleep(3)  # 使用 asyncio.sleep(), 它返回的是一個可等待的對象
        print('washer1 finished')
    
    async def washing2():
        await asyncio.sleep(2)
        print('washer2 finished')
    
    async def washing3():
        await asyncio.sleep(5)
        print('washer3 finished')
    
    """ 事件循環機制分為以下幾步驟: 1. 創建一個事件循環 2. 將異步函數加入事件隊列 3. 執行事件隊列, 直到最晚的一個事件被處理完畢后結束 4. 最后建議用 close() 方法關閉事件循環, 以徹底清理 loop 對象防止誤用 """
    # 1. 創建一個事件循環
    loop = asyncio.get_event_loop()
    
    # 2. 將異步函數加入事件隊列
    tasks = [
        washing1(),
        washing2(),
        washing3(),
    ]
    
    # 3. 執行事件隊列, 直到最晚的一個事件被處理完畢后結束
    loop.run_until_complete(asyncio.wait(tasks))
    """ PS: 如果不滿意想要 "多洗幾遍", 可以多寫幾句: loop.run_until_complete(asyncio.wait(tasks)) loop.run_until_complete(asyncio.wait(tasks)) loop.run_until_complete(asyncio.wait(tasks)) ... """
    
    # 4. 如果不再使用 loop, 建議養成良好關閉的習慣
    # (有點類似於文件讀寫結束時的 close() 操作)
    loop.close()
    
    """ 最終的打印效果: washer2 finished washer1 finished washer3 finished elapsed time = 5.126561641693115 (畢竟切換線程也要有點耗時的) 說句題外話, 我看有的博主的加入事件隊列是這樣寫的: tasks = [ loop.create_task(washing1()), loop.create_task(washing2()), loop.create_task(washing3()), ] 運行的效果是一樣的, 暫不清楚為什么他們這樣做. """


if __name__ == '__main__':
    # 為驗證是否真的縮短了時間, 我們計個時
    start = time()
    
    # demo1() # 需花費10秒
    # demo2() # 會報錯: RuntimeWarning: coroutine ... was never awaited
    # demo3() # 會報錯: RuntimeWarning: coroutine ... was never awaited
    demo4()  # 需花費5秒多一點點
    
    end = time()
    print('elapsed time = ' + str(end - start))


參考

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM