python3.8異步入門

概述

雖然協程很簡單,但是要用好很難,這篇文章主要是梳理python3.8提供的協程方法、

啓動

啓動協程主要有如下方法:

run-啓動單任務

啓動單任務

import asyncio
import time
async def say_after(delay, what):
    await asyncio.sleep(delay)
    print(what)
async def main():
    print(f"started at {time.strftime('%X')}")
    await say_after(1, 'hello')
    await say_after(2, 'world')
    print(f"finished at {time.strftime('%X')}")
asyncio.run(main())

create_task-異步執行

該函數主要功能是非阻塞地開啓一個協程任務,類比就相當於啓動了一個線程。啓動多任務,注意,需要在協程單任務裏面啓動。


async def main():
    # 創建一個協程任務,
    task1 = asyncio.create_task(
        say_after(1, 'hello'))
    task2 = asyncio.create_task(
        say_after(2, 'world'))
    print(f"started at {time.strftime('%X')}")
    # Wait until both tasks are completed (should take
    # around 2 seconds.)
    # 執行創建的任務,注意這裏沒有括號,這種執行方式是非阻塞的
    await task1
    await task2
    print(f"finished at {time.strftime('%X')}")

3.8中也可以給task增加一個任務名稱(暫時不知道有卵用)

gather-併發任務

awaitable asyncio.gather(*aws, loop=None, return_exceptions=False)->list
併發 運行 aws 序列中的 可等待對象。

return_exceptions =False (默認):,所引發的首個異常會立即傳播給等待 gather() 的任務。aws 序列中的其他可等待對象 不會被取消 並將繼續運行。
如果 return_exceptions 爲 True,異常會和成功的結果一樣處理,並聚合至結果列表。

如果 gather() 被取消,所有被提交 (尚未完成) 的可等待對象也會 被取消。

如果 aws 序列中的任一 Task 或 Future 對象 被取消,它將被當作引發了 CancelledError 一樣處理 – 在此情況下 gather() 調用 不會 被取消。這是爲了防止一個已提交的 Task/Future 被取消導致其他 Tasks/Future 也被取消。

示例:

import asyncio

async def factorial(name, number):
    f = 1
    for i in range(2, number + 1):
        print(f"Task {name}: Compute factorial({i})...")
        await asyncio.sleep(1)
        f *= i
    print(f"Task {name}: factorial({number}) = {f}")

async def main():
    # Schedule three calls *concurrently*:
    await asyncio.gather(
        factorial("A", 2),
        factorial("B", 3),
        factorial("C", 4),
    )

asyncio.run(main())

# Expected output:
#
#     Task A: Compute factorial(2)...
#     Task B: Compute factorial(2)...
#     Task C: Compute factorial(2)...
#     Task A: factorial(2) = 2
#     Task B: Compute factorial(3)...
#     Task C: Compute factorial(3)...
#     Task B: factorial(3) = 6
#     Task C: Compute factorial(4)...
#     Task C: factorial(4) = 24

在 3.7 版更改: 如果 gather 本身被取消,則無論 return_exceptions 取值爲何,消息都會被傳播。

sleep-休眠

asyncio.sleep(delay, result=None, *, loop=None)
阻塞 delay 指定的秒數。

如果指定了 result,則當協程完成時將其返回給調用者。
sleep() 總是會掛起當前任務,以允許其他任務運行。

3.8版本棄用
以下協程示例運行 5 秒,每秒顯示一次當前日期:

import asyncio
import datetime
async def display_date():
    loop = asyncio.get_running_loop()
    end_time = loop.time() + 5.0
    while True:
        print(datetime.datetime.now())
        if (loop.time() + 1.0) >= end_time:
            break
        await asyncio.sleep(1)
asyncio.run(display_date())

結束

shield-防止任務被取消

awaitable asyncio.shield(aw, *, loop=None)
保護一個 可等待對象 防止其被 取消。

如果 aw 是一個協程,它將自動作爲任務加入日程。
以下語句:
res = await shield(something())等價:res = await something()
不同之處 在於如果包含它的協程被取消,在 something() 中運行的任務不會被取消。從 something() 的角度看來,取消操作並沒有發生。然而其調用者已被取消,因此 “await” 表達式仍然會引發 CancelledError。

如果通過其他方式取消 something() (例如在其內部操作) 則 shield() 也會取消。

如果希望完全忽略取消操作 (不推薦) 則 shield() 函數需要配合一個 try/except 代碼段,如下所示:

try:
    res = await shield(something())
except CancelledError:
    res = None

wait_for-超時

asyncio.wait_for(aw, timeout, *, loop=None)
等待 aw 可等待對象 完成,指定 timeout 秒數後超時。
timeout 可以爲 None,也可以爲 float 或 int 型數值表示的等待秒數。如果 timeout 爲 None,則等待直到完成。

如果發生超時,任務將取消並引發 asyncio.TimeoutError.
要提示超時但避免避免任務 取消,可以加上 shield()。
如果等待被取消,則 aw 指定的對象也會被取消。

示例:

async def eternity():
    # Sleep for one hour
    await asyncio.sleep(3600)
    print('yay!')

async def main():
    # Wait for at most 1 second
    try:
        await asyncio.wait_for(eternity(), timeout=1.0)
    except asyncio.TimeoutError:
        print('timeout!')

asyncio.run(main())

# Expected output:
#
#     timeout!

wait-等待

coroutine asyncio.wait(aws, *, loop=None, timeout=None, return_when=ALL_COMPLETED)
併發運行 aws 指定的 可等待對象 並阻塞線程直到滿足 return_when 指定的條件。

返回兩個 Task/Future 集合: (done, pending)。

用法:
done, pending = await asyncio.wait(aws)
如指定 timeout (float 或 int 類型) 則它將被用於控制返回之前等待的最長秒數。
請注意此函數不會引發 asyncio.TimeoutError。當超時發生時,未完成的 Future 或 Task 將在指定秒數後被返回。

return_when 指定此函數應在何時返回。它必須爲以下常數之一:

常數 描述
FIRST_COMPLETED 函數將在任意可等待對象結束或取消時返回。
FIRST_EXCEPTION 函數將在任意可等待對象因引發異常而結束時返回。當沒有引發任何異常時它就相當於 ALL_COMPLETED。
ALL_COMPLETED 函數將在所有可等待對象結束或取消時返回。

與 wait_for() 不同,wait() 在超時發生時不會取消可等待對象。

async def foo():
    return 42

task = asyncio.create_task(foo())
done, pending = await asyncio.wait({task})

if task in done:
    # Everything will work as expected now.

線程交互

太複雜暫時不考慮

內省

asyncio.current_task(loop=None)
返回當前運行的 Task 實例,如果沒有正在運行的任務則返回 None。
如果 loop 爲 None 則會使用 get_running_loop() 獲取當前事件循環。
asyncio.all_tasks(loop=None)
返回事件循環所運行的未完成的 Task 對象的集合。
如果 loop 爲 None,則會使用 get_running_loop() 獲取當前事件循環。

Task對象

class asyncio.Task(coro, *, loop=None, name=None)
一個與 Future 類似 的對象,可運行 Python 協程。非線程安全。

Task 對象被用來在事件循環中運行協程。如果一個協程在等待一個 Future 對象,Task 對象會掛起該協程的執行並等待該 Future 對象完成。當該 Future 對象 完成,被打包的協程將恢復執行。

事件循環使用協同日程調度: 一個事件循環每次運行一個 Task 對象。而一個 Task 對象會等待一個 Future 對象完成,該事件循環會運行其他 Task、回調或執行 IO 操作。

使用高層級的 asyncio.create_task() 函數來創建 Task 對象,也可用低層級的 loop.create_task() 或 ensure_future() 函數。不建議手動實例化 Task 對象。

要取消一個正在運行的 Task 對象可使用 cancel() 方法。調用此方法將使該 Task 對象拋出一個 CancelledError 異常給打包的協程。如果取消期間一個協程正在等待一個 Future 對象,該 Future 對象也將被取消。

cancelled() 可被用來檢測 Task 對象是否被取消。如果打包的協程沒有抑制 CancelledError 異常並且確實被取消,該方法將返回 True。
asyncio.Task 從 Future 繼承了其除 Future.set_result() 和 Future.set_exception() 以外的所有 API。

cancel()

請求取消 Task 對象。
這將安排在下一輪事件循環中拋出一個 CancelledError 異常給被封包的協程。
協程在之後有機會進行清理甚至使用 try … … except CancelledError … finally 代碼塊抑制異常來拒絕請求。不同於 Future.cancel(),Task.cancel() 不保證 Task 會被取消,雖然抑制完全取消並不常見,也很不鼓勵這樣做。

以下示例演示了協程是如何偵聽取消請求的:

async def cancel_me():
    print('cancel_me(): before sleep')

    try:
        # Wait for 1 hour
        await asyncio.sleep(3600)
    except asyncio.CancelledError:
        print('cancel_me(): cancel sleep')
        raise
    finally:
        print('cancel_me(): after sleep')

async def main():
    # Create a "cancel_me" Task
    task = asyncio.create_task(cancel_me())

    # Wait for 1 second
    await asyncio.sleep(1)

    task.cancel()
    try:
        await task
    except asyncio.CancelledError:
        print("main(): cancel_me is cancelled now")

asyncio.run(main())

# Expected output:
#
#     cancel_me(): before sleep
#     cancel_me(): cancel sleep
#     cancel_me(): after sleep
#     main(): cancel_me is cancelled now

cancelled()

如果 Task 對象 被取消 則返回 True。

當使用 cancel() 發出取消請求時 Task 會被 取消,其封包的協程將傳播被拋入的 CancelledError 異常。

done()

如果 Task 對象 已完成 則返回 True。
當 Task 所封包的協程返回一個值、引發一個異常或 Task 本身被取消時,則會被認爲 已完成。

result()

返回 Task 的結果。

如果 Task 對象 已完成,其封包的協程的結果會被返回 (或者當協程引發異常時,該異常會被重新引發。)

如果 Task 對象 被取消,此方法會引發一個 CancelledError 異常。
如果 Task 對象的結果還不可用,此方法會引發一個 InvalidStateError 異常。

exception()

返回 Task 對象的異常。
如果所封包的協程引發了一個異常,該異常將被返回。如果所封包的協程正常返回則該方法將返回 None。

如果 Task 對象 被取消,此方法會引發一個 CancelledError 異常。
如果 Task 對象尚未 完成,此方法將引發一個 InvalidStateError 異常。

add_done_callback(callback, *, context=None)

添加一個回調,將在 Task 對象 完成 時被運行。
此方法應該僅在低層級的基於回調的代碼中使用。
要了解更多細節請查看 Future.add_done_callback() 的文檔。

remove_done_callback(callback)

從回調列表中移除 callback 指定的回調。
此方法應該僅在低層級的基於回調的代碼中使用。
要了解更多細節請查看 Future.remove_done_callback() 的文檔。

get_stack(*, limit=None)

返回此 Task 對象的棧框架列表。
如果所封包的協程未完成,這將返回其掛起所在的棧。如果協程已成功完成或被取消,這將返回一個空列表。如果協程被一個異常終止,這將返回回溯框架列表。

框架總是從按從舊到新排序。

每個被掛起的協程只返回一個棧框架。

可選的 limit 參數指定返回框架的數量上限;默認返回所有框架。返回列表的順序要看是返回一個棧還是一個回溯:棧返回最新的框架,回溯返回最舊的框架。(這與 traceback 模塊的行爲保持一致。)

print_stack(*, limit=None, file=None)

打印此 Task 對象的棧或回溯。

此方法產生的輸出類似於 traceback 模塊通過 get_stack() 所獲取的框架。

limit 參數會直接傳遞給 get_stack()。

file 參數是輸出所寫入的 I/O 流;默認情況下輸出會寫入 sys.stderr。

get_coro()

返回由 Task 包裝的協程對象。

3.8 新版功能.

get_name()

返回 Task 的名稱。

如果沒有一個 Task 名稱被顯式地賦值,默認的 asyncio Task 實現會在實例化期間生成一個默認名稱。

3.8 新版功能.

set_name(value)

設置 Task 的名稱。

value 參數可以爲任意對象,它隨後會被轉換爲字符串。

在默認的 Task 實現中,名稱將在任務對象的 repr() 輸出中可見。

3.8 新版功能.

classmethod all_tasks(loop=None)

返回一個事件循環中所有任務的集合。

默認情況下將返回當前事件循環中所有任務。如果 loop 爲 None,則會使用 get_event_loop() 函數來獲取當前事件循環。

Deprecated since version 3.7, will be removed in version 3.9: 請不要將此方法作爲任務方法來調用。 應當改用 asyncio.all_tasks() 函數。

classmethod current_task(loop=None)

返回當前運行任務或 None。

如果 loop 爲 None,則會使用 get_event_loop() 函數來獲取當前事件循環。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章