1. 程式人生 > 實用技巧 >python3.7中的async和await解析

python3.7中的async和await解析

參考自: https://blog.csdn.net/songjinxaing/article/details/103883210
感謝作者分享,此處記錄以免丟失

通過程式碼來解說原理
async def foo():
    print('----start foo')
    await asyncio.sleep(1)
    print('----end foo')

/*
預期想要的結果
----start foo
(等待一秒)
----end foo
*/

這個函式表示,先列印start foo 然後等待一秒, 然後再列印end foo
這個函式不能直接被執行. 它必須放在一個非同步環境中才能執行. 這個非同步環境獨立在整個程式之外,可以把所有的非同步環境打包成一個箱子, 看成是一個同步事件.
把這個函式裝在這個非同步環境裡 非同步環境的長度取決於環境裡需要執行事件最長的那個函式
開啟這個非同步環境的標誌是 asyncio.run(foo())

這條命令執行了之後,非同步環境就被開啟了. 需要主要的事, 同一執行緒同一時間只能開啟一個非同步環境. 換句話說, 在run函式裡面的函式(本例中為bar())裡面不能再包含run函式.

因此, 上例需要執行的話:

async def foo():
    print('start foo')
    await asyncio.sleep(1)
    print('----end foo')

if __name__ == '__main__':
    asyncio.run(foo())

例2

def foo2():
    print('----start foo')
    time.sleep(1)
    print('----end foo')

def bar2():
    print('----start bar')
    time.sleep(2)
    print('----end bar')

if __name__ == '__main__':
    foo2()
    bar2()

/*
預期輸出:
----start foo
(等待1秒)
----end foo
----start bar
(等待2秒)
----end bar
*/

把上面的函式改寫成非同步之後

我們想要的結果是

----start foo

****start bar

(等待1秒)

----end foo

(等待1秒)

****end bar

但是執行上面的程式 結果卻是

----start foo

(等待1秒)

----end foo

****start bar

(等待2秒)

****end bar

這是為什麼呢

await表示 等待後面的非同步函式操作完了之後, 執行下面的語句.

所以在在本例中,await foo 等待foo函式完全結束了之後, 再去執行

那麼如何一起執行呢

基本的有兩種方法

1.採用函式gather

官方文件中的解釋是

awaitable asyncio.gather(*aws, loop=None, return_exceptions=False)

併發 執行 aws 序列中的 可等待物件。

如果 aws 中的某個可等待物件為協程,它將自動作為一個任務加入日程。

如果所有可等待物件都成功完成,結果將是一個由所有返回值聚合而成的列表。結果值的順序與 aws 中可等待物件的順序一致。

如果 return_exceptions 為 False (預設),所引發的首個異常會立即傳播給等待 gather() 的任務。aws 序列中的其他可等待物件 不會被取消 並將繼續執行。

如果 return_exceptions 為 True,異常會和成功的結果一樣處理,並聚合至結果列表。

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

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

因此程式碼就有了

async def foo():
    print('----start foo')
    await asyncio.sleep(1)
    print('----end foo')

async def bar():
    print('****start bar')
    await asyncio.sleep(2)
    print('****end bar')

async def main():
    res = await asyncio.gather(foo(), bar())
    print(res)

if __name__ == '__main__':
    asyncio.run(main())

返回值為函式的返回值列表 本例中為[None, None]
第二種方法 建立task
asyncio.create_task(coro)

將 coro 協程 打包為一個 Task 排入日程準備執行。返回 Task 物件。

該任務會在 get_running_loop() 返回的loop中執行,如果當前執行緒沒有在執行的loop則會引發 RuntimeError。

此函式 在 Python 3.7 中被加入。在 Python 3.7 之前,可以改用低層級的 asyncio.ensure_future() 函式。

async def foo():
    print('----start foo')
    await asyncio.sleep(1)
    print('----end foo')

async def bar():
    print('****start bar')
    await asyncio.sleep(2)
    print('****end bar')

async def main():
    asyncio.create_task(foo())
    asyncio.create_task(bar())

if __name__ == '__main__':
    asyncio.run(main())

但是執行一下就會發現, 只輸出了

----start foo
****start bar

這是因為,create_task函式只是把任務打包放進了佇列,至於它們有沒有執行完. 不管.

因此需要等待它們執行完畢.

最後的程式碼為

async def foo():
    print('----start foo')
    await asyncio.sleep(1)
    print('----end foo')

async def bar():
    print('****start bar')
    await asyncio.sleep(2)
    print('****end bar')

async def main():
    task1 = asyncio.create_task(foo())
    task2 = asyncio.create_task(bar())

    await task1
    await task2

if __name__ == '__main__':
    asyncio.run(main())

如果有多個請求

async def foo():
    print('----start foo')
    await asyncio.sleep(1)
    print('----end foo')

async def main():
    tasks = []
    for i in range(10):
        tasks.append(asyncio.create_task(foo()))
    await asyncio.wait(tasks)

if __name__ == '__main__':
    asyncio.run(main())

async def foo():
    print('----start foo')
    await asyncio.sleep(1)
    print('----end foo')

async def bar():
    print('****start bar')
    await asyncio.sleep(2)
    print('****end bar')

async def main():
    tasks = []
    for i in range(10):
        tasks.append(asyncio.create_task(foo()))
    for j in range(10):
        tasks.append(asyncio.create_task(bar()))
    await asyncio.wait(tasks)

if __name__ == '__main__':
    asyncio.run(main())

非同步巢狀

async def foo():
    print('----start foo')
    await asyncio.sleep(1)
    print('----end foo')

async def bar():
    print('****start bar')
    await asyncio.sleep(2)
    print('****end bar')

async def foos():
    print('----------------------')
    tasks = []
    for i in range(3):
        tasks.append(asyncio.create_task(foo()))
    await asyncio.wait(tasks)

async def main():
    tasks = []
    for i in range(3):
        tasks.append(asyncio.create_task(foos()))
    for j in range(3):
        tasks.append(asyncio.create_task(bar()))
    await asyncio.wait(tasks)

if __name__ == '__main__':
    asyncio.run(main())

把每一個create_task當成新增了一條線. 這條線如果遇到IO操作了(即遇到了await) 那麼就先等待在這裡, 先執行別的線上的操作(如果已經有了結果)

create了線才可以跳來跳去, 如果不create, 是不會跳走的

async def foo():
    print('----start foo')
    await asyncio.sleep(1)
    print('----end foo')

async def foos():
    print('----------------------')
    tasks = []
    await foo()
    await foo()
    await foo()

async def main():
    tasks = []
    for i in range(3):
        tasks.append(asyncio.create_task(foos()))
    await asyncio.wait(tasks)

if __name__ == '__main__':
    asyncio.run(main())

這個例子裡面 只創造了3條線, 因此只能3個3個執行, 其實應該9個一起等, 但是因為沒有create_task所以並不會一起執行