# Asynchronous

在一開始開發程式時，「自動」執行批次任務相對比「手動」下，怎麼樣都覺得很有效率，但當程式開發到一定程度時，某些專案就會開始要求速度，例如我近幾個月都在開發選擇權造市程式，這個時候執行速度就是相當重要啦！

一般正常狀態撰寫的程式是由上而下的線性執行，當執行無誤完成第一步後，才會接著執行下一步。如果任務之間沒有相依性，我們可以如何增加效率呢？

我們先舉一個生活上的例子——「煮飯」，近期作為外派菜鳥的我正在學習如何自己煮飯，畢竟在台灣的都會區生活實在太方便了，到了異國只能自己來才吃得到想吃的味道。而我目前的階段屬於煮飯菜鳥，一次不能做太多事，免得手忙腳亂全部搞砸，所以一次只能做一件事情，因為很多事情都沒做過也不知道時間處理會如何。

#### Lv 1 廚房菜鳥：(Single thread)

* 一次處理一件任務：例如先洗菜，再煮一鍋水，再把菜放下去燙；洗米、泡米、再放入電鍋煮。
* 假設每件任務都是獨立且依序執行

#### Lv 2 廚房進階菜鳥：(Asynchronous)

* 區別什麼任務會花比較久的時間，但又不用自己實際盯場，例如電鍋煮飯。
* 當在執行等待任務完成的時間，就可以先去做其他任務，例如洗完米按下電鍋後，就可以來準備煎荷包蛋或是其餘備料，等飯煮好時，再回來處理飯的其他流程。

#### Lv 3 廚房老手：(Multiple threads & Asynchronous)

* 可以區分什麼任務會花比較久的等待時間。
* 可同時處理多項任務，例如可以同時煮麵跟煎荷包蛋，但必須要有多個瓦斯爐或鍋子。

> :joy: 這些都是我在煮飯時悟出的程式執行道理！

回到Python的世界，我們先使用廚房菜鳥的模式，我們的範例是煮咖啡與烤貝果，如下面的範例，煮咖啡要3分鐘，而烤貝果則需要5分鐘，如果我們一項接著一項做，總共需要8分鐘左右才能吃到一個早餐組合。

#### 一般模式（非使用Async)：

```python
import time

def brew_coffee():
    print("Start brew coffee")
    time.sleep(3 * 60)
    print("End brew coffee")
    return "coffee ready"
    
def toast_bagel():
    print("Start toast bagel")
    time.sleep(5 * 60)
    print("End toast bagel")
    return "bagel toasted"
    
def main():
    start_time = time.time()
    
    result_coffee = brew_coffee()
    result_bagel = toast_bagel()
    
    end_time = time.time()
    elapsed_time = end_time - start_time
    
    print(f"Result of brew_coffee: {result_coffee}")
    print(f"Result of toast bagel: {result_bagel}")
    print(f"Total execution time: {elapsed_time:2f} seconds")

if __name__ == "__main__":
    main()
```

第二種模式比較屬於正常人的模式，我們知道咖啡機可能要花一些時間預備，所以啟動後，這時候我們也趕快把貝果從冰箱拿出來放到烤箱，並將時間設定5分鐘，就讓烤箱開始烤，而咖啡機這時候可能也差不多好了，再回到咖啡機去處理磨豆等任務，當烤箱叮的一聲完成後，咖啡與烤貝果等任務其實也完成了，這個就是Async的概念。

#### Async模式

```python
import asyncio
import time

async def brew_coffee():
    print("Start brew coffee")
    await asyncio.sleep(3 * 60)
    print("End brew coffee")
    return "coffee ready"
    
async def toast_bagel():
    print("Start toast bagel")
    await asyncio.sleep(5 * 60)
    print("End toast bagel")
    return "bagel toasted"
    
async def main():
    start_time = time.time()
    
    # method 1: asyncio.gather()
    batch = asyncio.gather(brew_coffee(), toast_bagel())
    result_coffee, result_bagel = await batch
    
    # method 2: asyncio.create_task()
    # coffee_task = asyncio.create_task(brew_coffee())
    # toast_task = asyncio.create_task(toast_bagel())
    
    # result_coffee = await coffee_task
    # result_toast = await toast_task
    
    end_time = time.time()
    elapsed_time = end_time - start_time
    
    print(f"Result of brew_coffee: {result_coffee}")
    print(f"Result of toast bagel: {result_bagel}")
    print(f"Total execution time: {elapsed_time:2f} seconds")
    
if __name__ == "__main__":
    asyncio.run(main())
```

Asynchronous是近年比較常使用的加速處理模式，主要用來加速多項耗時較長的任務，例如資料庫I/O操作與爬蟲等任務，而這兩項任務都是等待其他服務的回應，而非本機端在進行處理。所以想透過Async加速的第一步就是識別專案的任務有哪些，個別分析耗時的狀況。

另一個系列加速處理的就是multiprocessing和threading，這兩項屬於平行同步處理，可能會導致一些資源消耗或併發一些問題，例如瞬間CPU達到極限或是記憶體佔用率過高，使得全部程序都停擺，而我自己的選擇權造市則使用混合模式執行。

***

### Reference

1. [Socratica - AsyncIO, await, and async - Concurrency in Python](https://www.youtube.com/watch?v=K56nNuBEd0c)

{% embed url="<https://www.youtube.com/watch?v=K56nNuBEd0c>" %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://go.raymondctw.dev/technology/python/speed-up-python/asynchronous.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
