python3 協程asyncio 調用步驟、阻塞和await、task任務、future對象 partial

首先介紹一下 偏函數

如果需要減少某個函數的參數個數,你可以使用

  • functools.partial()

【作用一】:partial() 函數允許你給一個或多個參數設置固定的值,減少接下來被調用時的參數個數。

【作用二】:partial() 用於固定某些參數,並返回一個新的callable對象。


關於協程的調用步驟

下面將簡單介紹asyncio的使用。

  • 🌰event_loop 事件循環:程序開啓一個無限的循環,程序員會把一些函數註冊到事件循環上。當滿足事件發生的時候,調用相應的協程函數。

  • 🌰coroutine 協程:協程對象,指一個使用async關鍵字定義的函數,它的調用不會立即執行函數,而是會返回一個協程對象。協程對象需要註冊到事件循環,由事件循環調用。

  • 🌰task 任務:一個協程對象就是一個原生可以掛起的函數,任務則是對協程進一步封裝,其中包含任務的各種狀態。

  • 🌰future對象: 代表將來執行或沒有執行的任務的結果。它和task上沒有本質的區別;

  • 🌰async/await 關鍵字:python3.5 用於定義協程的關鍵字,async定義一個協程,await用於掛起阻塞的異步調用接口。

重點注意:

1.當我們給一個函數添加了async關鍵字,或者使用asyncio.coroutine裝飾器裝飾,就會把它變成一個異步函數。
2.每個線程有一個事件循環,主線程調用asyncio.get_event_loop時會創建事件循環;

3.將任務封裝爲集合asyncio.gather(*args),之後一起傳入事件循環中;

4.要把異步的任務丟給這個循環的run_until_complete方法,事件循環會安排協同程序的執行。和方法名字一樣,該方法會等待異步的任務完全執行纔會結束。

【註釋】實現協程的不僅僅是asyncio,tornado和gevent都能實現類似的功能。

來看一個實例🌰:

async def test1():
    print("1")

async def test2():
    print("2")

a = test1()
b = test2()

try:
    a.send(None) # 可以通過調用 send 方法,執行協程函數
except StopIteration as e:
    print(e.value)
    # 協程函數執行結束時會拋出一個StopIteration 異常,標誌着協程函數執行結束,返回值在value中
    pass
try:
    b.send(None) # 可以通過調用 send 方法,執行協程函數
except StopIteration:
    print(e.value)
    # 協程函數執行結束時會拋出一個StopIteration 異常,標誌着協程函數執行結束,返回值在value中
    pass

---
輸出:
1
2

【解釋】程序先執行了test1函數,等到test1函數執行完後再執行test2函數。從執行過程上來看目前協程函數與普通函數沒有區別,並沒有實現異步函數。


關於阻塞和await

【概念】使用async關鍵字定義的協程對象,使用await可以針對耗時的操作進行掛起(是生成器中的yield的替代,但是本地協程函數不允許使用),讓出當前控制權。協程遇到await,事件循環將會掛起該協程,執行別的協程,直到其他協程也掛起,或者執行完畢,在進行下一個協程的執行。

import asyncio

async def test1():
    print("1")
    await asyncio.sleep(1) # asyncio.sleep(1)返回的也是一個協程對象
    print("2")

async def test2():
    print("3")
    print("4")

a = test1()
b = test2()

try:
    a.send(None) # 可以通過調用 send 方法,執行協程函數
except StopIteration:
    # 協程函數執行結束時會拋出一個StopIteration 異常,標誌着協程函數執行結束
    pass

try:
    b.send(None) # 可以通過調用 send 方法,執行協程函數
except StopIteration:
    pass

---
輸出:
1
3
4

【解釋】程序先執行test1協程函數,在執行到await時,test1函數停止了執行(阻塞);接着開始執行test2協程函數,直到test2執行完畢。從結果中,我們可以看到,直到程序運行完畢,test1函數也沒有執行完(沒有執行print(“2”))


【對上面的代碼塊進行修改】,使得test1函數完全執行

import asyncio
async def test1():
    print("1")
    await test2()
    print("2")

async def test2():
    print("3")
    print("4")

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

---
輸出:
1
3
4
2

【事件循環方法】asyncio.get_event_loop方法可以創建一個事件循環,然後使用 run_until_complete 將協程註冊到事件循環,並啓動事件循環。

【技能升級】使用async可以定義協程對象,使用await可以針對耗時的操作進行掛起,就像生成器裏的yield一樣,函數讓出控制權。協程遇到await,事件循環將會掛起該協程,執行別的協程,直到其他的協程也掛起或者執行完畢,再進行下一個協程的執行,協程的目的也是讓一些耗時的操作異步化


關於task任務

由於協程對象不能直接運行,在註冊事件循環的時候,其實是run_until_complete方法將協程包裝成爲了一個任務(task)對象。所謂task對象是Future類的子類,保存了協程運行後的狀態,用於未來獲取協程的結果。我們也可以手動將協程對象定義成task,改進代碼如下:

【對上面的代碼塊再一次修改】

import asyncio

async def test1():
    print("1")
    await test2()
    print("2")

async def test2():
    print("3")
    print("4")

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

【重點回顧】前面說到task對象保存了協程運行的狀態,並且可以獲取協程函數運行的返回值;

關於task對象那麼具體該如何獲取呢?

  • 需要綁定回調函數

  • 直接在運行完task任務後輸出

【提升】如果使用send方法執行函數,則返回值可以通過捕捉StopIteration異常,利用StopIteration.value獲取。

【舉個例子】:🌰

import asyncio

async def test_one():
  print("Zurich")
  await test_two()
  print("Alzacar")
  return "stop"

async def test_two():
  print("--")
  print("$$$")

loop = asyncio.get_event_loop()
task = asyncio.ensure_future(test_one())
loop.run_until_complete(task)
print(task.result())

---
【Output】:
Zurich
Alzacar
--
$$$

使用future對象

【解釋】:future對象有幾個狀態:Pending、Running、Done、Cancelled。創建future的時候,task爲pending,事件循環調用執行的時候當然就是running,調用完畢自然就是done,如果需要停止事件循環,就需要先把task取消,可以使用asyncio.Task獲取事件循環的task。

【程序:關於future對象的使用】

import asyncio
import functools

async def test1():
    print("1")
    await test2()
    print("2")
    return "stop"

async def test2():
    print("3")
    print("4")

def callback(param1,param2,future):
    print(param1,param2)
    print('Callback:',future.result())

loop = asyncio.get_event_loop()
task = asyncio.ensure_future(test1())
task.add_done_callback(functools.partial(callback,"param1","param2"))# 綁定回調函數
loop.run_until_complete(task)

【說明】通過future對象的result方法可以獲取協程函數的返回值,創建task。test1()是一個協程對象
回調函數中的future對象就是創建的task對象
【多插播一句】:回調函數如果需要接受多個參數,可以通過偏函數導入。


【關於協程的停止】

怎麼停止執行協程呢?
【第一步】:需要先取消task
【第二步】:停止loop事件循環。

import asyncio

async def test1():
    print("1")
    await asyncio.sleep(3)
    print("2")
    return "stop"

tasks = [
    asyncio.ensure_future(test1()),
    asyncio.ensure_future(test1()),
    asyncio.ensure_future(test1()),
]

loop = asyncio.get_event_loop()
try:
    loop.run_until_complete(asyncio.wait(tasks))
except KeyboardInterrupt as e:
    for task in asyncio.Task.all_tasks():
        task.cancel()
    loop.stop()
    loop.run_forever()
finally:
    loop.close()

 

 

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