python異步編程之asyncio高階API

image

asyncio 高階API列表

asyncio中函數可以分爲高階函數和低階函數。低階函數用於調用事件循環、linux 套接字、信號等更底層的功能,高階函數是屏蔽了更多底層細節的任務併發,任務執行函數。通常開發中使用更多的是高階函數。本篇主要介紹asyncio中常用的高階函數。
由於asyncio在不同的版本中有差異,本文以及本系列都以python3.10爲準。

函數 功能
run() 創建事件循環,運行一個協程,關閉事件循環。
create_task() 創建一個asyncio的Task對象
await sleep() 休眠幾秒
await gather() 併發執行所有事件的調度和等待
await wait_for() 有超時控制的運行
await shield() 屏蔽取消操作
await wait() 完成情況的監控器
current_task() 返回當前Task對象
all_tasks() 返回事件循環中所有的task對象
Task Task對象
to_thread() 在不同的 OS 線程中異步地運行一個函數
run_coroutine_threadsafe() 從其他OS線程中調度一個協程
for in as_completed() 用 for 循環監控完成情況

run

函數原型:

asyncio.run(coro, *, debug=False)

功能:創建事件循環,運行傳入的協程。該函數總是會創建一個新的事件循環並在結束時關閉它,應該被當做asyncio程序的主入口點。run() 函數是用來創建事件,將task加入事件,運行事件的函數。

async def main():
    await asyncio.sleep(1)
    print('hello')

asyncio.run(main())

run() 從功能上等價於以下低階API。獲取一個事件循環,創建一個task,加入事件循環。

loop = asyncio.get_event_loop()
task = loop.create_task(main())
loop.run_until_complete(task)

create_task

函數原型:

asyncio.create_task(coro, *, name=None)

功能:將協程函數封裝成一個Task。協程函數沒有生命週期,但是Task有生命週期。
將協程打包爲一個 Task 並自動尋找事件循環加入。返回 Task 對象。該任務會在 get_running_loop() 返回的循環中執行,如果當前線程沒有在運行的循環則會引發 RuntimeError。

async def coro():
    await asyncio.sleep(1)
    print("i am coro")


async def main():
    task = asyncio.create_task(coro())
    print(f"task狀態:{task._state}")
    await asyncio.sleep(2)
    print(f"task狀態:{task._state}")
    print("i am main")

asyncio.run(main())

結果:

task狀態:PENDING
i am coro
task狀態:FINISHED
i am main

結果分析:
可以看到task運行中的狀態和結束的生命週期狀態

gather

函數原型:

asyncio.gather(*aws, return_exceptions=False)

功能:
併發執行所有可等待對象,收集任務結果,返回所有已經完成的task的結果。結果將是一個由所有返回值組成的列表。結果值的順序與傳入的task的順序一致。可等待對象可以是協程和task。
如果序列中是協程而不是task,那麼會將其自動封裝成task加入事件循環。

import asyncio

async def coro(value):
    print(f"hello coro{value}")
    return f"coro{value}"

async def main():
    tasks = [coro(i) for i in range(5)]
    res = await asyncio.gather(*tasks)
    for i in res:
        print(i)

asyncio.run(main())

結果:

hello coro0
hello coro1
hello coro2
hello coro3
hello coro4
coro0
coro1
coro2
coro3
coro4

結果分析:
獲取了所有協程的返回值,並且返回的順序和任務的順序一致。

wait

函數原型:

asyncio.wait(aws, *, timeout=None, return_when=ALL_COMPLETED)

功能:
併發地運行序列中的可等待對象,並進入阻塞狀態直到滿足 return_when 所指定的條件。將task任務結果收集起來,返回兩個 Task/Future 集合: (done, pending)。done是已經完成的任務,pending是未完成的任務,未完成的原因可能是超時或return_when策略。
aws:
aws中保存的是task而不是協程,從3.8起不建議傳入協程,3.11將不再支持傳入協程。
timeout:
如指定 timeout (float 或 int 類型) 則它將被用於控制返回之前等待的最長秒數。
請注意此函數不會引發 asyncio.TimeoutError。當超時發生時,未完成的 Future 或 Task 將不會繼續執行,不會返回結果。
return_when:
return_when 指定此函數應在何時返回。它必須爲以下參數之一:

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

基礎使用示例

import asyncio


async def coro(value):
    print(f"hello coro{value}")
    return f"coro{value}"

async def main():
    tasks = [asyncio.create_task(coro(i)) for i in range(5)]
    done, pending = await asyncio.wait(tasks)
    for i in done:
        print(i.result())

asyncio.run(main())

結果:

hello coro0
hello coro1
hello coro2
hello coro3
hello coro4
coro1
coro2
coro0
coro3
coro4

結果分析:
返回結果和執行順序並不是一致的

指定超時時間

import asyncio
from asyncio import FIRST_COMPLETED

async def coro(value):
    print(f"hello coro{value}")
    await asyncio.sleep(value)
    return f"coro{value}"

async def main():
    tasks = [asyncio.create_task(coro(i)) for i in range(5)]
    done, pending = await asyncio.wait(tasks, timeout=3)

    print("---------finish----------")
    for i in done:
        print(i.result())

    print("---------pending----------")
    for i in pending:
        print(i)

asyncio.run(main())
hello coro0
hello coro1
hello coro2
hello coro3
hello coro4
---------finish----------
coro1
coro2
coro0
---------pending----------
<Task pending name='Task-5' coro=<coro() running at /Users/ljk/Documents/code/daily_dev/async_demo/wait_demo.py:6> wait_for=<Future finished result=None>>
<Task pending name='Task-6' coro=<coro() running at /Users/ljk/Documents/code/daily_dev/async_demo/wait_demo.py:6> wait_for=<Future pending cb=[Task.task_wakeup()]>>

結果分析:
超時未完成的task會保存在pending中,未完成的task在超時之後不會繼續執行,沒有返回結果。

return_when配置任意任務完成就返回

import asyncio
from asyncio import FIRST_COMPLETED

async def coro(value):
    print(f"hello coro{value}")
    await asyncio.sleep(value)
    return f"coro{value}"

async def main():
    tasks = [asyncio.create_task(coro(i)) for i in range(5)]
    done, pending = await asyncio.wait(tasks, return_when=FIRST_COMPLETED)

    print("---------finish----------")
    for i in done:
        print(i.result())

    print("---------pending----------")
    for i in pending:
        print(i)

asyncio.run(main())


結果:

hello coro0
hello coro1
hello coro2
hello coro3
hello coro4
---------finish----------
coro0
---------pending----------
<Task pending name='Task-5' coro=<coro() running at /Users/ljk/Documents/code/daily_dev/async_demo/wait_demo.py:6> wait_for=<Future pending cb=[Task.task_wakeup()]>>
<Task pending name='Task-3' coro=<coro() running at /Users/ljk/Documents/code/daily_dev/async_demo/wait_demo.py:6> wait_for=<Future pending cb=[Task.task_wakeup()]>>
<Task pending name='Task-4' coro=<coro() running at /Users/ljk/Documents/code/daily_dev/async_demo/wait_demo.py:6> wait_for=<Future pending cb=[Task.task_wakeup()]>>
<Task pending name='Task-6' coro=<coro() running at /Users/ljk/Documents/code/daily_dev/async_demo/wait_demo.py:6> wait_for=<Future pending cb=[Task.task_wakeup()]>>

結果分析:
獲取到任意結果就返回,未完成的task保存在pending中。未完成的task在超時之後不會繼續執行。

as_completed

函數原型:

asyncio.as_completed(aws, *, timeout=None)

說明:併發執行aws中保存的可等待對象,返回一個協程的迭代器。可以從迭代器中取出最先執行完成的task的結果。返回結果和執行順序不一致。aws中可以是task或協程序列。

import asyncio


async def coro(value):
    print(f"hello coro{value}")
    return f"coro{value}"

async def main():
    tasks = [coro(i) for i in range(5)]
    for item in asyncio.as_completed(tasks):
        res = await item
        print(res)


asyncio.run(main())

結果:

hello coro2
hello coro3
hello coro4
hello coro1
hello coro0
coro2
coro3
coro4
coro1
coro0

結果分析:
所有任務都會執行完成,沒有超時配置。返回順序和執行順序無關。

gather、wait、as_completed 異同點小結

asyncio協程體系中可以實現創建多個任務併發執行的函數有以下三個:

  • asyncio.gather
  • asyncio.wait
  • asyncio.as_completed

不同之處比較:

特性/函數 gather wait as_completed
入參 同時支持task和協程序列 只支持task序列 同時支持task和協程序列
獲取結果順序 有序,和併發序列順序相同 無序,和併發序列無關 無序,和併發序列無關
返回 返回結果列表,保存的是函數返回值。 返回元組done、pending。元組中保存的是task,而非task 的函數返回值 返回一個迭代器,從中可迭代出函數返回值。

wait for

函數原型:

asyncio.wait_for(aw, timeout)

功能:執行單個可等待對象,指定 timeout 秒數後超時
等待可等待對象完成,指定timeout秒數後超時。和gather類似,可以自動將協程轉化成任務加入循環。
timeout 可以爲 None,也可以爲 float 或 int 型數值表示的等待秒數。如果 timeout 爲 None,則等待直到完成。
如果發生超時,任務將取消並引發 asyncio.TimeoutError。

async def coro():
    # 睡眠5s
    await asyncio.sleep(3600)
    print('finish!')

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

asyncio.run(main())

結果:

timeout!

高階API中常用的函數基本就是這些,下一篇分析低階函數。

連載一系列關於python異步編程的文章。包括同異步框架性能對比、異步事情驅動原理等。歡迎關注微信公衆號第一時間接收推送的文章。

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