asyncio基礎篇01

在asyncio的前篇中我們已經知道yield到asyncIO的演化路線
現在我們就來了解asyncio的基本使用

event_loop 事件循環:程序開啓一個無限的循環,程序員會把一些函數(協程)註冊到事件循環上。當滿足事件發生的時候,調用相應的協程函數。
coroutine 協程:協程對象,指一個使用async關鍵字定義的函數,它的調用不會立即執行函數,而是會返回一個協程對象。協程對象需要註冊到事件循環,由事件循環調用。
future 對象:代表將來執行或沒有執行的任務的結果。它和task上沒有本質的區別
task 任務:一個協程對象就是一個原生可以掛起的函數,任務則是對協程進一步封裝,其中包含任務的各種狀態。Task 對象是 Future 的子類,它將 coroutine 和 Future 聯繫在一起,將 coroutine 封裝成一個 Future 對象。
async/await 關鍵字:python3.5 用於定義協程的關鍵字,async定義一個協程,await用於掛起阻塞的異步調用接口。其作用在一定程度上類似於yield。

import asyncio

async def hello(name):
print(‘Hello,’, name)

#定義協程對象
coroutine = hello(“World”)

#定義事件循環對象容器
loop = asyncio.get_event_loop()
#task = asyncio.ensure_future(coroutine)

#將協程轉爲task任務
task = loop.create_task(coroutine)

#將task任務扔進事件循環對象中並觸發
loop.run_until_complete(task)

await用於掛起阻塞的異步調用接口。其作用在一定程度上類似於yield。
注意這裏是,一定程度上,意思是效果上一樣(都能實現暫停的效果),但是功能上卻不兼容。就是你不能在生成器中使用await,也不能在async 定義的協程中使用yield。

在這裏插入圖片描述
除此之外呢,還有一點很重要的。
yield from 後面可接 可迭代對象,也可接future對象/協程對象;
await 後面必須要接 future對象/協程對象

綁定回調函數

異步IO的實現原理,就是在IO高的地方掛起,等IO結束後,再繼續執行。在絕大部分時候,我們後續的代碼的執行是需要依賴IO的返回值的,這就要用到回調了。
回調的實現,有兩種,一種是絕大部分程序員喜歡的,利用的同步編程實現的回調。 這就要求我們要能夠有辦法取得協程的await的返回值。

import asyncio
import time


async def _sleep(x):
    time.sleep(2)
    return '暫停{}秒'.format(x)


loop = asyncio.get_event_loop()
task = asyncio.ensure_future(_sleep(2))
loop.run_until_complete(task)

#取得返回結果
print(task.result())

運行結果
暫停2秒

  • 還有一種是通過asyncio自帶的添加回調函數功能來實現。
import asyncio
import time


async def _sleep(x):
    time.sleep(2)
    return '暫停{}秒'.format(x)

def callback(future):
    print("這裏是回調函數,獲取返回結果是:",future.result())

loop = asyncio.get_event_loop()
task = asyncio.ensure_future(_sleep(2))

#回調函數,自動將future傳遞進去
task.add_done_callback(callback)

loop.run_until_complete(task)

運行結果:
這裏是回調函數,獲取返回結果是: 暫停2秒

上面講述的是協程中的單任務,而很多情況下我們其實是要進行多任務的,否則異步IO就沒意義了

協程中的併發
asyncio實現併發,就需要多個協程來完成任務,每當有任務阻塞的時候就await,然後其他協程繼續工作。
我們採用wait和gather來處理多個併發

import asyncio

async def do_some_work(x):
    print('waiting',x)
    await asyncio.sleep(x)
    return 'Done after {}s'.format(x)

#協程對象
coroutine1 = do_some_work(1)
coroutine2 = do_some_work(2)
coroutine3 = do_some_work(3)

tasks = [
    asyncio.ensure_future(coroutine1),
    asyncio.ensure_future(coroutine2),
    asyncio.ensure_future(coroutine3)
]

loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

#loop.run_until_complete(asyncio.gather(*tasks))
for i in tasks:
    print(i.result())


協程中的嵌套

使用async可以定義協程,協程用於耗時的io操作,我們也可以封裝更多的io操作過程,這樣就實現了嵌套的協程,即一個協程中await了另外一個協程,如此連接起來。

import asyncio


# 用於內部的協程函數
async def do_some_work(x):
    print('Waiting', x)
    await asyncio.sleep(x)
    return 'Done after {}s'.format(x)


# 用於外部的協程函數
async def main():
    # 創建協程對象
    coroutine1 = do_some_work(1)
    coroutine2 = do_some_work(2)
    coroutine3 = do_some_work(3)

    tasks = [
        asyncio.ensure_future(coroutine1),
        asyncio.ensure_future(coroutine2),
        asyncio.ensure_future(coroutine3)
    ]
    # dones表示已經完成的任務
    # pendings 表示未完成的任務,因爲wait是可以設定等待時間的
    dones, pendings = await asyncio.wait(tasks)
    for i in dones:
        print("task ret:", i.result())


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

源碼當中也是嵌套的:

asyncio.await()的源碼# 內部協程函數
async def _wait(fs, timeout, return_when, loop):
    assert fs, 'Set of Futures is empty.'
    waiter = loop.create_future()
    timeout_handle = None
    if timeout is not None:
        timeout_handle = loop.call_later(timeout, _release_waiter, waiter)
    counter = len(fs)

    def _on_completion(f):
        nonlocal counter
        counter -= 1
        if (counter <= 0 or
            return_when == FIRST_COMPLETED or
            return_when == FIRST_EXCEPTION and (not f.cancelled() and
                                                f.exception() is not None)):
            if timeout_handle is not None:
                timeout_handle.cancel()
            if not waiter.done():
                waiter.set_result(None)

    for f in fs:
        f.add_done_callback(_on_completion)

    try:
        await waiter
    finally:
        if timeout_handle is not None:
            timeout_handle.cancel()

    done, pending = set(), set()
    for f in fs:
        f.remove_done_callback(_on_completion)
        if f.done():
            done.add(f)
        else:
            pending.add(f)
    return done, pending

# 外部協程函數
async def wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED):
    if futures.isfuture(fs) or coroutines.iscoroutine(fs):
        raise TypeError(f"expect a list of futures, not {type(fs).__name__}")
    if not fs:
        raise ValueError('Set of coroutines/Futures is empty.')
    if return_when not in (FIRST_COMPLETED, FIRST_EXCEPTION, ALL_COMPLETED):
        raise ValueError(f'Invalid return_when value: {return_when}')

    if loop is None:
        loop = events.get_event_loop()

    fs = {ensure_future(f, loop=loop) for f in set(fs)}
    # 【重點】:await一個內部協程
    return await _wait(fs, timeout, return_when, loop)

協程中的狀態
生成器的時候,有提及過生成器的狀態。同樣,在協程這裏,我們也瞭解一下協程(準確的說,應該是Future對象,或者Task任務)有哪些狀態。
Pending:創建future,還未執行
Running:事件循環正在調用執行任務
Done:任務執行完畢
Cancelled:Task被取消後的狀態

gather與wait的的深入理解

還是照例用例子來說明,先定義一個協程函數
import asyncio

async def factorial(name, number):
f = 1
for i in range(2, number+1):
print(“Task %s: Compute factorial(%s)…” % (name, i))
await asyncio.sleep(1)
f *= i
print(“Task %s: factorial(%s) = %s” % (name, number, f))

  • 接受參數方面的差異:

asyncio.wait
接收的tasks,必須是一個list對象,這個list對象裏,存放多個的task。
它可以這樣,用asyncio.ensure_future轉爲task對象

tasks=[
       asyncio.ensure_future(factorial("A", 2)),
       asyncio.ensure_future(factorial("B", 3)),
       asyncio.ensure_future(factorial("C", 4))
]

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.wait(tasks))

也可以這樣,不轉爲task對象。
loop = asyncio.get_event_loop()

tasks=[
       factorial("A", 2),
       factorial("B", 3),
       factorial("C", 4)
]

loop.run_until_complete(asyncio.wait(tasks))

asyncio.gather
接收的就比較廣泛了,他可以接收list對象,但是 * 不能省略

tasks=[
       asyncio.ensure_future(factorial("A", 2)),
       asyncio.ensure_future(factorial("B", 3)),
       asyncio.ensure_future(factorial("C", 4))
]

loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.gather(*tasks))

還可以這樣,和上面的 * 作用一致,這是因爲asyncio.gather()的第一個參數是 *coros_or_futures,它叫 非命名鍵值可變長參數列表,可以集合所有沒有命名的變量。
loop = asyncio.get_event_loop()

loop.run_until_complete(asyncio.gather(
    factorial("A", 2),
    factorial("B", 3),
    factorial("C", 4),
))

甚至還可以這樣
loop = asyncio.get_event_loop()

group1 = asyncio.gather(*[factorial("A" ,i) for i in range(1, 3)])
group2 = asyncio.gather(*[factorial("B", i) for i in range(1, 5)])
group3 = asyncio.gather(*[factorial("B", i) for i in range(1, 7)])

loop.run_until_complete(asyncio.gather(group1, group2, group3))
  • 返回結果不同

asyncio.wait 返回dones和pendings
dones:表示已經完成的任務
pendings:表示未完成的任務
如果我們需要獲取,運行結果,需要手工去收集獲取。

asyncio.gather
它會把值直接返回給我們,不需要手工去收集。
results = await asyncio.gather(*tasks)

for result in results:
print('Task ret: ', result)

  • wait有控制功能,是wait的優點
import asyncio
import random


async def coro(tag):
    await asyncio.sleep(random.uniform(0.5, 5))


loop = asyncio.get_event_loop()

tasks = [coro(i) for i in range(1, 11)]
# 【控制運行任務數】:運行第一個任務就返回
# FIRST_COMPLETED :第一個任務完全返回
# FIRST_EXCEPTION:產生第一個異常返回
# ALL_COMPLETED:所有任務完成返回 (默認選項)
dones, pendings = loop.run_until_complete(asyncio.wait(tasks,return_when="FIRST_COMPLETED"))

print("第一次完成的任務量:",len(dones))

dones2,pendings2 = loop.run_until_complete(asyncio.wait(pendings,timeout=1))

print("第二次完成的任務量:",len(dones2))

dones3,pendings3 = loop.run_until_complete(asyncio.wait(pendings2))

print("第三次完成的任務量:",len(dones3))

loop.close()

運行結果:
第一次完成的任務數: 1
第二次完成的任務數: 4
第三次完成的任務數: 5

以上是asyncio的基本用法,而其實在實際當中asyncio並不是單獨使用,現在很多異步開發的庫都是以asyncio爲基礎的。

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