asyncio非同步模組的21個協程編寫例項

秋葉紅了發表於2020-08-25
  1. 啟動一個無返回值協程

    通過async關鍵字定義一個協程

    import sys
    import asyncio
    
    async def coroutine():
        print('執行協程')
        
    if sys.version_info >= (3, 7, 0):
        asyncio.run(coroutine())
    else:
        loop = asyncio.get_event_loop()
        loop.run_until_complete(coroutine())
        loop.close()
    

    輸出結果如下

    執行協程
    
  2. 啟動一個有返回值協程

    import sys
    import asyncio
    
    async def coroutine():
        print('執行協程')
        return 'done!'
      
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(coroutine())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(coroutine())
        loop.close()
    print(result)
    

    輸出結果如下

    執行協程
    done!
    
  3. 使用await關鍵字

    await關鍵字只能在一個協程內部使用

    await等待另一個協程任務結果,它不會阻塞事件迴圈,但會在阻塞當前協程的上下文

    import sys
    import asyncio
    
    async def coroutine():
        print('執行協程 排程asyncio.sleep協程任務')
        await asyncio.sleep(2)
        return 'done!'
      
    print('啟動時間:', time.time())
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(coroutine())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(coroutine())
        loop.close()
    print(result)
    print('結束時間:', time.time())
    

    輸出結果如下

    啟動時間: 1598236154.90588
    執行協程 排程asyncio.sleep協程任務
    done!
    結束時間: 1598236156.9118028
    
  4. 回撥函式: 迅速回撥

    迅速回撥是立即主動呼叫一個方法

    回撥方法必然是一個普通函式

    回撥通過事件迴圈loop提供的方法新增,它不會阻塞協程上下文

    同一個協程內的回撥函式之間存在阻塞關係,不同協程內的回撥函式之間無阻塞關係

    import sys
    import asyncio
    
    def callback(a, c=2):
        time.sleep(c)
        print(f"傳入引數a={a}, c={c}, 時間{time.time()}")
    
    async def main():
        print(f'新增回撥, 時間{time.time()}')
        loop = asyncio.get_running_loop()
        loop.call_soon(callback, 1)
        loop.call_soon(callback, 2, 0)
        print(f'協程結束, 時間{time.time()}')
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    新增回撥, 時間1598236958.119624
    協程結束, 時間1598236958.1196742
    傳入引數a=1, c=2, 時間1598236960.1198878
    傳入引數a=2, c=0, 時間1598236960.119946
    
  5. 回撥函式:延時回撥

    延時回撥可以指定在多久後執行回撥函式

    延時回撥的其他特性和迅速回撥一致

    特別注意,如果事件迴圈內的協程已經執行結束,尚在等待呼叫的回撥函式不會被呼叫

    import sys
    import asyncio
    
    def callback(a, c=3):
        time.sleep(c)
        print(f"傳入引數a={a}, c={c}, 時間{time.time()}")
    
    async def main():
        print(f'新增回撥, 時間{time.time()}')
        loop = asyncio.get_running_loop()
        loop.call_later(1, callback, 2, 0)
        loop.call_soon(callback, 1)
    
        print(f'協程結束, 時間{time.time()}')
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    新增回撥, 時間1598237313.375363
    協程結束, 時間1598237313.375415
    傳入引數a=1, c=3, 時間1598237316.376031
    傳入引數a=2, c=0, 時間1598237316.376389
    

    解釋說明,對於協程main,call_later和call_soon是不會阻塞協程上下文的,因此兩個回撥函式可以視為同時加入了事件迴圈的回撥,其中回撥callback(1)方法是立即呼叫,回撥函式會sleep 3秒,而回撥方法callback(2, 0)是在協程延時 1秒後呼叫,但回撥函式之間是存在阻塞關係的,因此它會等待callback(1)先執行結束,然後判斷是否滿足了延時條件再執行。

  6. 回撥函式:定時回撥

    此處的指定時間並非系統時間戳,而是指事件迴圈建立的時間戳

    通過loop.time()獲取事件迴圈時間戳

    import sys
    import asyncio
    
    def callback(a, loop, c=3):
        time.sleep(c)
        print(f"傳入引數a={a}, c={c}, 時間{loop.time()}")
    
    
    async def main():
        loop = asyncio.get_running_loop()
        now = loop.time()
        print(f'事件迴圈時間戳{now}')
        loop.call_at(now + 4, callback, 2, loop, 1)
        loop.call_soon(callback, 1, loop)
        await asyncio.sleep(5)
        print(f'協程結束, 時間戳{loop.time()}')
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    
  7. asyncio.Future物件

    Future的例項化物件可以認為是一個協程物件,它可以使用await關鍵字

    通過延時回撥+Future可以模擬一個協程的邏輯

    即延時回撥 ≈ 非同步等待返回結果

    Future ≈ 非阻塞模型,它不會阻塞事件迴圈的其他協程

    import sys
    import asyncio
    
    def mark_done(future: asyncio.Future, result):
        print(f'標記future結束  時間{time.time()}')
        future.set_result(result)
    
    
    async def main():
        print(f'協程開始  時間{time.time()}')
        loop = asyncio.get_running_loop()
        future = asyncio.Future()
        loop.call_later(3, mark_done, future, 'done!')
        result = await future
        print(f'future結果 {result}  時間{time.time()}')
    
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    協程開始  時間1598238858.6768472
    標記future結束  時間1598238861.678293
    future結果 done!  時間1598238861.678438
    
  8. Future回撥

    Future的回撥是在一個Future物件標記結束後執行的函式

    await會等待回撥函式執行結束,即回撥函式會在協程上下文阻塞

    import sys
    import asyncio
    
    def callback(future: asyncio.Future):
        # 基於Future的回撥函式一定要接受一個Future物件
        print(f'Future回撥被呼叫 時間{time.time()}')
        time.sleep(1)
    
    
    async def main():
        print(f'協程開始  時間{time.time()}')
        loop = asyncio.get_running_loop()
        future = asyncio.Future()
        future.add_done_callback(callback)
        loop.call_later(1, lambda future: future.set_result('done'), future)
        result = await future
        print(f'future結果 {result} 時間{time.time()}')
    
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    協程開始  時間1598241100.534262
    Future回撥被呼叫 時間1598241101.539032
    future結果 done 時間1598241102.5411909
    
  9. aysncio.Task物件

    Task物件在呼叫create_task會立即執行,它類似迅速回撥

    asyncio.create_task是python3.7引入的高階api,3.7以下使用ensure_future方法

    與回撥函式的區別:

    • 回撥函式一定不是協程,而Task物件只能建立協程任務
    • 回撥函式的傳入引數直接通過新增回撥的方法傳入,而任務物件直接傳入協程形參
    • 回撥函式之間執行是阻塞的,而Task物件則是基於事件迴圈的標準協程

    在協程上下文中可以使用await關鍵字等待任務結果

    import sys
    import asyncio
    
    async def task_func(n):
        print(f'執行 task_func  時間{time.time()}')
        await asyncio.sleep(n)
        return 'task done!'
    
    
    async def main():
        loop = asyncio.get_running_loop()
        task = loop.create_task(task_func(3))
        result = await task
        print(f'task結果 {result} 時間{time.time()}')
    
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    執行 task_func  時間1598241581.334356
    task結果 task done! 時間1598241584.338337
    
  10. Task取消任務

    可以取消一個正在事件迴圈內執行的task物件

    Task和Future一樣,支援通過add_done_callback新增回撥函式

    import sys
    import asyncio
    
    async def task_func(n):
        print(f'執行 task_func  時間{time.time()}')
        await asyncio.sleep(n)
        return 'task done!'
    
    
    async def main():
        loop = asyncio.get_running_loop()
        task = asyncio.create_task(task_func(5))
        loop.call_later(3, lambda task: task.cancel(), task)
        # Task被取消 會丟擲CancelledError異常
        try:
            result = await task
            print(f'task結果 {result} 時間{time.time()}')
        except asyncio.exceptions.CancelledError:
            print(f'task被取消了 時間{time.time()}')
    
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    執行 task_func  時間1598242256.3299708
    task被取消了 時間1598242259.335436
    
  11. 使用asyncio.ensure_future建立任務

    asyncio.ensure_future實現效果和asyncio.create_task一致

    import sys
    import asyncio
    
    async def task_func(n):
        print(f'執行 task_func  時間{time.time()}')
        await asyncio.sleep(n)
        return 'task done!'
    
    
    async def main():
        print(f'協程開始  時間{time.time()}')
        task = asyncio.ensure_future(task_func(2))
        result = await task
        print(f'task結果 {result} 時間{time.time()}')
    
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    協程開始  時間1598249260.1346428
    執行 task_func  時間1598249260.1347158
    task結果 task done! 時間1598249262.137878
    
  12. asyncio.wait執行多個協程

    asyncio.wait接受一個協程列表/元組將其加入事件迴圈

    返回兩個列表,分別包含已完成和正在執行的Future物件

    asyncio.wait 會阻塞協程上下文直至滿足指定的條件(預設條件所有協程執行結束)

    wait支援設定一個超時時間,但在超時發生時不會取消可等待物件,但若事件迴圈結束時未完成則會丟擲CancelledError異常。如果要超時主動取消,可用wait_for方法

    asyncio.wait 返回的結果集是按照事件迴圈中的任務完成順序排列的,所以通常和原始任務順序不同

    import sys
    import asyncio
    
    async def corn_sleep(n):
        try:
            await asyncio.sleep(n)
        except asyncio.exceptions.CancelledError:
            print(f'corn({n})超時取消!  時間{time.time()}')
        print(f'corn({n}) done!  時間{time.time()}')
        return f'corn({n}) done!  時間{time.time()}'
    
    
    async def main():
        print(f'協程開始  時間{time.time()}')
        tasks = [corn_sleep(i) for i in range(1, 6)]
        done, pending = await asyncio.wait(tasks, timeout=3)
        for task in done:
            pass
            # print(task.result())
        for task in pending:
            print(task.done())
        await asyncio.sleep(1)
        print(f'協程結束  時間{time.time()}')
    
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    協程開始  時間1598253388.614105
    corn(1) done!  時間1598253389.6196852
    corn(2) done!  時間1598253390.6171541
    False
    False
    False
    corn(3) done!  時間1598253391.617078
    corn(4) done!  時間1598253392.617147
    協程結束  時間1598253392.617195
    corn(5)超時取消!  時間1598253392.6173718
    corn(5) done!  時間1598253392.6173818
    
  13. asyncio.gather執行多個協程

    gather方法和wait都可以執行多個協程,但輸入和輸出有所差異

    • 輸出差異,gather保證了結果列表的順序,它是嚴格遵循傳入任務順序的
    • 輸入差異,wait方法接受的是一個協程列表,而gather是通過可變長引數傳入協程方法的
    • wait支援超時設定,gather無法設定超時時間
    • wait返回兩個列表,列表元素是Future物件,gather只返回done列表,列表元素是Future物件的result()結果
    import sys
    import asyncio
    
    async def corn_sleep(n):
        await asyncio.sleep(n)
        return f'corn({n}) done!  時間{time.time()}'
    
    
    async def main():
        print(f'協程開始  時間{time.time()}')
        tasks = [corn_sleep(i) for i in range(1, 6)]
        done = await asyncio.gather(*tasks)
        for result in done:
            print(result)
        print(f'協程結束  時間{time.time()}')
    
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    協程開始  時間1598250547.5619462
    corn(1) done!  時間1598250548.567125
    corn(2) done!  時間1598250549.5660028
    corn(3) done!  時間1598250550.563731
    corn(4) done!  時間1598250551.566098
    corn(5) done!  時間1598250552.566897
    協程結束  時間1598250552.566983
    
  14. asyncio.as_completed執行多個協程

    as_completed方法功能同wait和gather都可以用於執行多個協程

    as_completed接受的是協程列表,返回的是一個迭代器,迭代元素為Future物件

    as_completed方法支援超時設定,但它會在協程上下文丟擲asyncio.exceptions.TimeoutError錯誤

    as_completed方法返回結果集是無序的

    import sys
    import asyncio
    
    async def corn_sleep(n):
        await asyncio.sleep(n)
        return f'corn({n}) done!  時間{time.time()}'
    
    
    async def main():
        print(f'協程開始  時間{time.time()}')
        tasks = [corn_sleep(i) for i in range(1, 6)]
        for task in asyncio.as_completed(tasks, timeout=3.5):
            try:
                result = await task
                print(result)
            except asyncio.exceptions.TimeoutError:
                print(f'協程超時了  時間{time.time()}')
                break
        print(f'協程結束  時間{time.time()}')
    
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    協程開始  時間1598251179.489662
    corn(1) done!  時間1598251180.493866
    corn(2) done!  時間1598251181.4911182
    corn(3) done!  時間1598251182.493823
    協程超時了  時間1598251182.991565
    協程結束  時間1598251182.991592
    
  15. 協程鎖

    相比執行緒、程式鎖,協程鎖似乎應用場景不那麼多,比如儲存檔案時?

    import sys
    import asyncio
    from functools import partial
    
    def callback(lock: asyncio.Lock):
        print(f'釋放鎖  時間{time.time()}')
        lock.release()
    
    
    async def corn1(x, loop, lock):
        async with lock:
            pass
        # 加鎖,利用延遲迴調1秒後解鎖
        await lock.acquire()
        loop.call_later(1, callback, lock)
        return f'{x} done! 時間{time.time()}'
    
    
    async def main():
        loop = asyncio.get_running_loop()
        lock = asyncio.Lock()
        await lock.acquire()
        print(f'加鎖 時間{time.time()}')
        loop.call_later(2, callback, lock)
        corn = partial(corn1, loop=loop, lock=lock)
        results = await asyncio.gather(corn(1), corn(2), corn(3))
        for result in results:
            print(result)
    
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    加鎖 時間1598251855.600318
    釋放鎖  時間1598251857.6041799
    釋放鎖  時間1598251858.606617
    釋放鎖  時間1598251859.609798
    1 done! 時間1598251857.604349
    2 done! 時間1598251858.606834
    3 done! 時間1598251859.6099358
    
  16. Event 事件物件

    Event物件和Lock物件有一定的相似性,可用於同步操作

    Event物件提供一個狀態標記位,它是一個布林值,只用來判斷狀態

    import sys
    import asyncio
    
    def task_done(event: asyncio.Event):
        event.set()
    
    
    async def corn(event: asyncio.Event):
        await event.wait()
        print(f'corn done!  時間{time.time()}')
    
    
    async def main():
        print(f'協程開始  時間{time.time()}')
        event = asyncio.Event()
        loop = asyncio.get_running_loop()
        loop.call_later(2, task_done, event)
        await corn(event)
    
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    協程開始  時間1598252539.2691798
    corn done!  時間1598252541.273142
    
  17. Queue 協程佇列

    與普通佇列相比,協程佇列有一個task_done方法,用於標記某一個任務結束,而join方法則會阻塞,直到滿足從佇列取出的個數等於task_done方法呼叫的次數

    import sys
    import asyncio
    
    async def producer(queue: asyncio.Queue):
        print('生產者 上班')
        for i in range(1, 6):
            await queue.put(f'產品({i})')
            await asyncio.sleep(2)
        await queue.put(None)
        await queue.join()
        print('生產者 打烊')
    
    
    async def consumer(x, queue: asyncio.Queue):
        print(f'消費者{x}號進場')
        while True:
            pt = await queue.get()
            queue.task_done()
            if pt is None:
                await queue.put(None)
                break
            else:
                print(f'{x}號 消費了 {pt}')
    
    
    async def main():
        loop = asyncio.get_running_loop()
        queue = asyncio.Queue(maxsize=3)
        loop.create_task(producer(queue))
        await asyncio.wait([consumer(i, queue) for i in range(3)])
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    輸出結果如下

    生產者 上班
    消費者2號進場
    2號 消費了 產品(1)
    消費者0號進場
    消費者1號進場
    2號 消費了 產品(2)
    0號 消費了 產品(3)
    1號 消費了 產品(4)
    2號 消費了 產品(5)
    生產者 下班
    
  18. asyncio.subprocess 非同步呼叫子程式

    asyncio提供create_subprocess_exec和create_subprocess_shell方法

    前者可以呼叫任意程式,後者則通過shell命令列呼叫其他程式

    import sys
    import asyncio
    
    async def get_date():
        code = 'import datetime; print(datetime.datetime.now())'
        task1 = asyncio.create_subprocess_exec(
            sys.executable, '-c', code,
            stdout=asyncio.subprocess.PIPE
        )
        task2 = asyncio.create_subprocess_shell(
            'ls -all ~/Desktop', stdout=asyncio.subprocess.PIPE
        )
        results = await asyncio.gather(task1, task2)
        response = []
        for proc in results:
            data = await proc.stdout.read()
            line = data.decode('utf8').rstrip()
            response.append(line)
        return '\n'.join(response)
    
    date = asyncio.run(get_date())
    print(f"當前時間: {date}")
    

    輸出結果如下

    當前時間: 2020-08-24 16:11:18.143943
    total 16
    drwxr-xr-x   3 sw  staff    96 Apr  6 09:11 $RECYCLE.BIN
    drwx------@  8 sw  staff   256 Jun 18 10:52 .
    drwxr-xr-x+ 63 sw  staff  2016 Aug 23 20:52 ..
    -rw-r--r--@  1 sw  staff  6148 Jul 22 17:54 .DS_Store
    -rw-r--r--   1 sw  staff     0 May 27  2019 .localized
    drwxr-xr-x   3 sw  staff    96 Feb  3  2020 Don't Starve Together.app
    drwxr-xr-x   9 sw  staff   288 May  3 18:24 HeavenMS
    drwxr-xr-x   3 sw  staff    96 Sep 20  2019 Tomb Raider.app
    
  19. 網路通訊-高階API

    asyncio封裝了幾個高階方法來快速實現網路通訊

    • await asyncio.open_connection() 建立TCP連線
    • await asyncio.open_unix_connection() 建立Unix socket連線
    • await start_server() 啟動TCP服務
    • await start_unix_server() 啟動Unix socket服務
    • StreamReader 接收網路資料的高階async/await物件
    • StreamWriter 傳送網路資料的高階async/await物件

    以搭建一個簡單http伺服器為例

    import sys
    import asyncio
    
    async def http_handle(render: asyncio.StreamReader,
                          writer: asyncio.WriteTransport):
        """在asyncio.start_server傳入該協程
        當一個tcp連線建立時就會呼叫該回撥,並傳入兩個引數
        :param render: StreamReader物件
        :param writer: StreamWriter物件
        StreamReader和StreamWriter物件都是繼承於Transport
        :return:
        """
        message = await render.read(1024)
        print(message.decode())
        writer.write(b'HTTP/1.1 200 OK\r\n\r\nHello Client!')
        writer.close()
    
    
    async def main():
        server = await asyncio.start_server(http_handle, '127.0.0.1', 8000)
        addr = server.sockets[0].getsockname()
        print(f'伺服器啟動 {addr}')
        async with server:
            await server.serve_forever()
    
    
    if sys.version_info >= (3, 7, 0):
        result = asyncio.run(main())
    else:
        loop = asyncio.get_event_loop()
        result = loop.run_until_complete(main())
        loop.close()
    

    瀏覽器開啟http://127.0.0.1:8000 控制檯輸出結果如下

    GET / HTTP/1.1
    Host: 127.0.0.1:8000
    Connection: keep-alive
    Cache-Control: max-age=0
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.135 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
    Sec-Fetch-Site: none
    Sec-Fetch-Mode: navigate
    Sec-Fetch-User: ?1
    Sec-Fetch-Dest: document
    Accept-Encoding: gzip, deflate, br
    Accept-Language: zh-CN,zh;q=0.9
    

    瀏覽器顯示結果

    Hello Client!
    
  20. 網路通訊-低階API

    案例19所用的方法是基於底層Transport和Protocol封裝的結果

    Protocol協議類是通過重寫一系列回撥方法來處理網路請求

    Transport例項可以簡單看作是對一個socket物件封裝,實現了資料收發功能

    除了網路通訊外,子程式(程式通訊)也是通過協議回撥來實現

    以一個tcp服務端為例,較常用的有4個回撥函式

    • connection_made() 連線建立時被呼叫
    • connection_lost() 連線丟失或關閉時被呼叫
    • data_received() 接收到資料時被呼叫
    • eof_received() 接收到EOF時被呼叫

    以案例19的http服務端為例,實現相同效果

    import sys
    import asyncio
    
    class HttpProtocol(asyncio.Protocol):
        def __init__(self):
            """每個tcp連線建立時都會例項化這個類"""
            self.transport: asyncio.Transport = None
    
        def connection_made(self, transport: asyncio.Transport):
            """連線建立時被呼叫"""
            self.transport = transport
    
        def data_received(self, data):
            """接收資料時被呼叫"""
            print(data.decode())
            self.transport.write(b'HTTP/1.1 200 OK\r\n\r\nHello Client!')
            self.transport.close()
    
    
    loop = asyncio.get_event_loop()
    server = loop.create_server(HttpProtocol, '127.0.0.1', 8000)
    loop.run_until_complete(server)
    loop.run_forever()
    
  21. 訊號處理

    事件迴圈能夠支援新增訊號來執行回撥函式

    import signal
    import asyncio
    
    def ask_exit(sig_name: str, loop: asyncio.AbstractEventLoop):
        print("捕獲訊號 %s: exit" % sig_name)
        loop.stop()
    
    
    async def main():
        loop = asyncio.get_running_loop()
        for signame in {'SIGINT', 'SIGTERM'}:
            loop.add_signal_handler(
                getattr(signal, signame),
                partial(ask_exit, signame, loop))
    
        await asyncio.sleep(3600)
    
    print("事件迴圈將在1小時後或者按下Ctrl+C停止")
    try:
        asyncio.run(main())
    except RuntimeError as e:
        if str(e) != 'Event loop stopped before Future completed.':
            raise
    

    輸出結果如下

    事件迴圈將在1小時後或者按下Ctrl+C停止
    ^C捕獲訊號 SIGINT: exit
    

高頻方法總結

  1. 回撥和Task具有一定相似性,回撥是函式,任務是協程

  2. 向事件迴圈非阻塞執行一個協程是建立一個任務,可以使用如下方法

    方法 傳入 返回 備註
    asyncio.create_task 協程 Task物件 高層級api、Python3.7 +
    asyncio.ensure_future 協程 Task物件 高層級api
    loop.create_task 協程 Task物件 低層級api
  3. 向事件迴圈新增多個協程(使用await關鍵詞才會實際新增執行),可使用如下方法

    方法 傳入 返回 備註
    asyncio.wait 協程列表 done/pending列表
    元素是Future物件
    支援超時設定,結果無序
    asyncio.gather 變長引數 協程返回值組成的列表 返回結果列表有序
    asyncio.as_completed 協程列表 協程任務迭代器 通過for迴圈遍歷獲取任務結果
  4. 在unix平臺,可以使用效能更好uvloop來替代asyncio預設的事件迴圈

    import uvloop
    asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
    

相關文章