python 協程用法總結(一)

Sprint_dV發表於2020-03-22

一、python協程關鍵字

async: 宣告協程

注意:通過async宣告的不是函式,單獨呼叫不會執行

await: 用來等待可等待的物件,包括協程(就是async宣告的協程)、任務(asyncio.create_task()、asyncio.ensure_future()建立的任務)和Future(是一個低層級的可等待物件,表示一個非同步操作的 最終結果,一般不要自己建立)

注意:當前協程會再await處等待阻塞(回去執行其他協程),知道等待的協程或任務完成

import asyncio

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

二、可等待物件

可等待 物件有三種主要型別: 協程, 任務 和 Future,可以用關鍵字await等待事件完成

協程

  • 協程函式: 定義形式為 async def 的函式;
  • 協程物件: 呼叫 協程函式 所返回的物件。

任務

  • 通過asyncio.create_task()將協程函式建立任務,該協程將自動排入日程準備立即執行
    注意: python >= 3.7
  • python3.6 使用asyncio.ensure_future()建立任務

Future

  • 是一種特殊的 低層級 可等待物件,表示一個非同步操作的 最終結果。
  • 當一個 Future 物件 被等待,這意味著協程將保持等待直到該 Future 物件在其他地方操作完畢。
  • 一般沒有必要在應用層級的程式碼中建立 Future 物件

可以使用一下方法

  • asyncio.gather(*coros_or_futures, loop=None, return_exceptions=False)

    1. 如果 coros_or_futures* 中的某個可等待物件為協程,它將自動作為一個任務加入日程
    2. 如果所有可等待物件都成功完成,結果將是一個由所有返回值聚合而成的列表。結果值的順序與 aws 中可等待物件的順序一致。
    3. 如果 return_exceptions 為 False (預設),所引發的首個異常會立即傳播給等待 gather() 的任務。aws 序列中的其他可等待物件 不會被取消 並將繼續執行。 如果 return_exceptions 為 True,異常會和成功的結果一樣處理,並聚合至結果列表。
    4. 如果 gather() 被取消,所有被提交 (尚未完成) 的可等待物件也會 被取消。
      如果 aws 序列中的任一 Task 或 Future 物件 被取消,它將被當作引發了 CancelledError 一樣處理 – 在此情況下 gather() 呼叫 不會 被取消。這是為了防止一個已提交的 Task/Future 被取消導致其他 Tasks/Future 也被取消。
  • asyncio.shield(arg, *, loop=None)

    1. 保護一個 可等待物件 防止其被 取消。
    2. arg是一個協程,它將自動作為任務加入日程。
  • asyncio.wait_for(fut, timeout, *, loop=None)

    1. 用來等待一個future設定timeout,timeout 可以為 None,也可以為 float 或 int 型數值表示的等待秒數。如果 timeout 為 None,則等待直到完成。
    2. 如果發生超時,任務將取消並引發 asyncio.TimeoutError.
    3. 要避免任務 取消,可以加上 shield()。
  • asyncio.wait(fs, *, loop=None, timeout=None, return_when=ALL_COMPLETED)

    import asyncio
    
    async def wait1():
        print('in 1')
        await asyncio.sleep(1)
        return 1
    
    async def wait2():
        print('in 2')
        await asyncio.sleep(2)
        return 2
  async def wait3():
      print('in 3')
      await asyncio.sleep(3)
      return 3


  async def main():
      done, pending = await asyncio.wait([wait1(), wait2(), wait3()], timeout=2.5)
      print(done,'\n', pending)

  asyncio.run(main())

結果
in 1
in 3
in 2
{<Task finished coro=<wait1() done, defined at /Users/zhangjintao/Desktop/隨筆/小測試程式碼/多工/協程/3.7/test.py:3> result=1>, <Task finished coro=<wait2() done, defined at /Users/zhangjintao/Desktop/隨筆/小測試程式碼/多工/協程/3.7/test.py:8> result=2>}
{<Task pending coro=<wait3() running at /Users/zhangjintao/Desktop/隨筆/小測試程式碼/多工/協程/3.7/test.py:16> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x10acc2650>()]>>}

  1. timeout 超時不會引發異常,超時會返回未完成的任務的狀態, done是完成的任務set,pending是未完成的任務set
  2. 如果timeout不設定,會預設所有完成後返回結果,pending返回的是空set

三、再執行緒池中執行程式碼

loop.run_in_executor`(executor, func, *args)

import asyncio
import concurrent.futures

def blocking_io():
    # File operations (such as logging) can block the
    # event loop: run them in a thread pool.
    with open('/dev/urandom', 'rb') as f:
        return f.read(100)

def cpu_bound():
    # CPU-bound operations will block the event loop:
    # in general it is preferable to run them in a
    # process pool.
    return sum(i * i for i in range(10 ** 7))

async def main():
    loop = asyncio.get_running_loop()

    ## Options:

    # 1. Run in the default loop's executor:
    result = await loop.run_in_executor(
        None, blocking_io)
    print('default thread pool', result)

    # 2. Run in a custom thread pool:
    with concurrent.futures.ThreadPoolExecutor() as pool:
        result = await loop.run_in_executor(
            pool, blocking_io)
        print('custom thread pool', result)

    # 3. Run in a custom process pool:
    with concurrent.futures.ProcessPoolExecutor() as pool:
        result = await loop.run_in_executor(
            pool, cpu_bound)
        print('custom process pool', result)

asyncio.run(main())

詳見:https://docs.python.org/zh-cn/3.7/library/...

本作品採用《CC 協議》,轉載必須註明作者和本文連結