【Python】async 是什麼

在撰寫自動化交易程式時,常常需要從交易所下載歷史資料但不希望下載資料的動作造成其他功能無法正常執行所以測試了一…

async 也翻譯成 非同步異步,首先我們要先探討 async多執行緒的差異

async V.S 多執行緒

如果以一間餐廳來舉例

  • 多執行緒:相當於聘請多個廚師,每個廚師負責各自的料理,互不干擾,理論上廚師越多,完成一桌滿漢全席的速度越快。而實務上,則取決於廚師的效率以及硬體資源,像是爐子有幾個,菜刀有幾把等等 ( CPU 等時脈、核心數…)
  • async:則著重於怎麼讓一個廚師在相同時間內可以完成更多的料理,例如以往廚師就是傻傻地站在鍋子前面等水滾,使用 async 後可以在等待的時間去做別的事情

簡單的範例程式

上面提到簡單的概念,下面我們就寫幾個簡單的程式來測試一下

案例一:未使用 async

import time
from config.logger import logger

def counter():
  # 印出0~4,每印出一個數字後,暫停一秒
  for i in range(5):  
    logger.info(i)
    time.sleep(1)

def printer():
  # 每印出一個 Printing... 字眼,就暫停0.5秒,總共印6次
  for i in range(5):
      logger.info('Printing...')
      time.sleep(0.5)

now = int(round(time.time())

counter()
printer()

# 統計程式執行時間
logger.info(f'經過 {int(round(time.time())) - now} 秒')

上面這個範例執行的結果如下

2023-06-02 16:53:59,249 <INFO> 0
2023-06-02 16:54:00,261 <INFO> 1
2023-06-02 16:54:01,266 <INFO> 2
2023-06-02 16:54:02,272 <INFO> 3
2023-06-02 16:54:03,278 <INFO> 4
2023-06-02 16:54:04,283 <INFO> Printing...
2023-06-02 16:54:04,788 <INFO> Printing...
2023-06-02 16:54:05,291 <INFO> Printing...
2023-06-02 16:54:05,797 <INFO> Printing...
2023-06-02 16:54:06,300 <INFO> Printing...
2023-06-02 16:54:06,804 <INFO> Printing...
2023-06-02 16:54:07,304 <INFO> 經過 8

可以看到程式就是先等 5 秒印出 1~5 後,在印出 Printing…,總共需花費 8 秒

案例二:使用 async

首先來看看改寫後的程式

import asyncio
import time
from config.logger import logger

# 改寫為 async function
async def async_counter():
  for i in range(5):
    logger.info(i)
    await asyncio.sleep(1)

# 改寫為 async function
async def async_printer():
  for i in range(1, 6):
    logger.info('Printing...')
    await asyncio.sleep(0.5)

# 需要呼叫 async Function 時,本身也需要宣告為 async Function
async def main():
  now = int(round(time.time()))
  
  # await 的作用是等待 async Function 執行完畢
  await asyncio.gather(async_counter(), async_printer())
  
  # 統計程式執行時間
  logger.info(f'經過 {int(round(time.time())) - now} 秒')

# 呼叫 async function 需要使用 asyncio.run
asyncio.run(main())

改為使用 Async 寫法後,程式執行結果如下

2023-06-02 14:56:22,464 <INFO> 0
2023-06-02 14:56:22,465 <INFO> Printing...
2023-06-02 14:56:22,979 <INFO> Printing...
2023-06-02 14:56:23,468 <INFO> 1
2023-06-02 14:56:23,468 <INFO> Printing...
2023-06-02 14:56:23,975 <INFO> Printing...
2023-06-02 14:56:24,480 <INFO> 2
2023-06-02 14:56:24,480 <INFO> Printing...
2023-06-02 14:56:25,475 <INFO> 3
2023-06-02 14:56:26,487 <INFO> 4
2023-06-02 14:56:27,497 <INFO> 經過 5

可以看到在印出 0~4 的過程中,會穿插 Printing… 訊息
代表程式已經不是傻傻地等待第一個 function 執行完
而是在等待過程中,先去執行別的工作
最終可以省下另一個工作所需的三秒時間

使用 await 等待 async function 完成

從上面的範例程式中可以看出,呼叫 async function 時,前面都會放 await,目的是為了等待 async function 都完成後再繼續往下執行後面的程式,需注意,必須在 async function 內才能使用 await

也能夠不使用 await ,改為使用 asyncio.create_task() 呼叫 async function,如下面範例

import asyncio
import time
from config.logger import logger

async def async_counter():
    for i in range(5):
        logger.info(i)
        await asyncio.sleep(1)

async def async_printer():
    for i in range(1, 6):
        logger.info('Printing...')
        await asyncio.sleep(0.5)


async def main():
    now = int(round(time.time()))
    task = asyncio.create_task(async_counter())
    logger.info('main')
    logger.info(f'經過 {int(round(time.time())) - now} 秒')

asyncio.run(main())

上面這個範例的輸出結果會如下

2023-06-02 17:09:54,109 <INFO> main
2023-06-02 17:09:54,110 <INFO> 經過 0
2023-06-02 17:09:54,110 <INFO> 0

為什麼只數到 0 呢?

因為程式內並沒有使用 await 去等待 async function 執行完畢,所以執行緒執行完每一行程式之後也不會去等 async function 完成就直接結束,在執行緒結束後,task 也自然無法繼續執行,因為如同前面所說,async 並不是 多執行緒

如果要等待 async function 完成後再讓執行緒關閉,還是得使用 await
將 main function 改為如下

async def main():
    now = int(round(time.time()))
    task = asyncio.create_task(async_counter())
    logger.info('main')
    # 等待非同步function完成
    await task
    logger.info(f'經過 {int(round(time.time())) - now} 秒')

印出來的結果就會是我們預期的結果

2023-06-02 17:17:31,823 <INFO> main
2023-06-02 17:17:31,824 <INFO> 0
2023-06-02 17:17:32,837 <INFO> 1
2023-06-02 17:17:33,851 <INFO> 2
2023-06-02 17:17:34,859 <INFO> 3
2023-06-02 17:17:35,864 <INFO> 4
2023-06-02 17:17:36,868 <INFO> 經過 5

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *