CodeGym /Java Adesua /Python SELF TW /非同步方法

非同步方法

Python SELF TW
等級 25 , 課堂 2
開放

3.1 模組 asyncio

自己為非同步子任務創建執行緒早就沒人這麼做了。嗯,也不是不能創建,但這樣的操作被認為是太低階的東西,只有框架開發者會用它,還是不得不用的時候。

現在流行的是 非同步編程,async/await 操作符coroutinestasks。先按部就班地來聊聊...

一點歷史背景

一開始在 Python 中,非同步編程的任務是用基於生成器的 coroutines 來解決的。 然後,在 Python 3.4 中,引入了 asyncio 模組(有時候也寫成 async IO),裡面實現了非同步編程的機制。 在 Python 3.5 中又出現了 async/await 的構造。

現在給你一點入門資訊。我會先簡單講一下這些東西,然後會詳細再詳細。因為這些東西幾乎都互相關聯,要想詳細解釋其中之一都要提到另一些。

模組 asyncio

模組 asyncio 是為撰寫非同步程序而設計的,能提供並行執行任務的能力。 它支援非同步 I/O 操作、計時器、套接字、執行 coroutines 和多執行緒,可以在一個或多個事件循環中工作。

Coroutines

Coroutines非同步函數,用關鍵字 async def 定義。 Coroutines 讓你可以用 關鍵字 await 中止執行這樣其他的 coroutines 就可以在這段時間執行


import asyncio

# 定義非同步函數(coroutine)
async def main():
    print('Hello ...')
    # 中止執行 1 秒
    await asyncio.sleep(1)
    print('... World!')

# 在事件循環中啟動非同步函數 main()
asyncio.run(main())

事件循環 (Event Loop)

事件循環管理 coroutines、tasks 和其他非同步操作的執行。調用 asyncio.run() 啟動事件循環, 並執行 coroutine 直至完成。


import asyncio

async def main():
    print('Hello ...')
    await asyncio.sleep(1)
    print('... World!')

# 獲取當前事件循環
loop = asyncio.get_event_loop()
# 執行 coroutine 直至完成
loop.run_until_complete(main())
# 完成所有任務後關閉事件循環
loop.close()

Tasks

Tasks 讓你可以並行地執行 coroutines。可以用 asyncio.create_task()asyncio.ensure_future() 創建。


import asyncio

# 定義一個將延遲執行的 coroutine
async def say_after(delay, what):
    # 延遲執行指定時間
    await asyncio.sleep(delay)
    print(what)

# 主要 coroutine
async def main():
    # 創建 tasks 並行執行 coroutines
    task1 = asyncio.create_task(say_after(1, 'hello'))
    task2 = asyncio.create_task(say_after(2, 'world'))
    
    # 等待兩個 tasks 完成
    await task1
    await task2

# 啟動主要 coroutine
asyncio.run(main())

Futures

Future 對象表示將在未來可用的非同步操作結果。 例如,它們用於等待非同步任務的完成。


import asyncio

# 定義一個模擬長時間任務的 coroutine
async def long_running_task():
    print('Task started')
    # 暫停執行 3 秒
    await asyncio.sleep(3)
    print('Task finished')
    return 'Result'

# 主要 coroutine
async def main():
    # 創建 future 以等待任務完成
    future = asyncio.ensure_future(long_running_task())
    # 等待任務完成並獲取結果
    result = await future  
    print(f'Task result: {result}')

# 啟動主要 coroutine
asyncio.run(main())

3.2 非同步函數 — async def

宣告非同步函數的方法跟宣告普通函數一樣,只是在關鍵字 def 之前加上 async


async def 函數名稱(參數):
    函數代碼

非同步函數的宣告就跟普通函數一樣,調用也一樣,但它返回的結果卻不同。如果調用非同步函數,它不會返回結果,而是返回一個特殊的對象—— coroutine

甚至可以這樣檢查:


import asyncio

async def main():
    print("Hello World")
            
# 調用非同步函數,返回 coroutine
result = main()
# 檢查結果類型
print(type(result)) # <class 'coroutine'>

發生了什麼事情?當你用 async 標記函數時,其實就是在它上面加上一個裝飾器,大概是這樣的:


def async_decorator(func):
    # 創建 Task 對象
    task = Task()
    # 將 func 傳入 task 以便執行
    task.target = func  
    # 將 task 加入事件循環的任務隊列
    eventloop.add_task(task)  
    # 返回 task 對象
    return task 

你的代碼就像:


import asyncio

@async_decorator
def main():
    print("Hello World")
            
result = main()
print(type(result)) # <class 'coroutine'>

這個類比的意義在於:

當你調用非同步函數時,創建了一個特殊的 Task 對象,它將在未來的某個時候執行你的函數。可能是 0.0001 秒後,也可能是 10 秒後。

這個 task 對象立即作為你非同步函數調用的結果返回給你。這就是 coroutine。 你的非同步函數可能還沒開始執行,而 task(coroutine)對象你已經有了。

為什麼要這個 task(coroutine)呢?你能做的事不多,但有三件事可以做:

  • 等待非同步函數的執行完成。
  • 等待非同步函數執行結束並從 coroutine 獲取函數的執行結果。
  • 等待 10 個(任意數目)非同步函數的執行完成。

如何做到這些,我下面會講。

3.3 操作符 await

大部分 coroutine 的操作都從「等待非同步函數的執行」開始。因此我們有一個特殊的操作符 await

你只要在 coroutine 前寫上它:


await coroutine

或者直接在調用非同步函數時寫上:


await 非同步函數(參數)

當 Python 在代碼中遇到 操作符 await,它會暫停當前函數的執行並等待,直到 coroutine 執行完畢——直到非同步函數完成。

重要! 操作符 await 只能用於 非同步函數內 以暫停執行,直到完成另一個 coroutine 或非同步操作。

這麼做是為了簡化在非同步函數調用間的切換。這種 await 調用實際上是一種聲明「我們這裡等著,不知道要等多久——去執行其他非同步函數吧」。

例子:


import asyncio

# 定義非同步函數
async def async_print(text):
    print(text)
        
# 主要非同步函數
async def main():
    # 使用 await 等待非同步函數的執行
    await async_print("Hello World")
        
# 啟動主事件循環並執行 coroutine main()
asyncio.run(main()) # 啟動非同步函數

實際上,操作符 await 更聰明——它還返回非同步函數執行的結果。

例子:


import asyncio

# 定義一個將兩個數字相加的非同步函數
async def async_add(a, b):
    return a + b
        
# 主要非同步函數
async def main():
    # 使用 await 獲取 async_add 的執行結果
    sum = await async_add(100, 200)
    print(sum)
        
# 啟動主事件循環並執行 coroutine main()
asyncio.run(main()) # 啟動非同步函數

總結一下,操作符 await

  • 暫停當前非同步函數,直到另一個 coroutine 或非同步操作完成。
  • 返回非同步操作或 coroutine 的執行結果。
  • 只能在非同步函數內使用。
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION