「解讀」輕鬆理解 Python 中的 async await 概念

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


「解讀」輕鬆理解 Python 中的 async await 概念

<code>from time import sleep, timedef 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> doesn't define '__await__'            x = await 3 + 2  # <class> 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))/<class>/<class>/<locals>/<locals>/<locals>/<code> 

原文鏈接:https://blog.csdn.net/Likianta/article/details/90123678


分享到:


相關文章: