ASP.NET MVC Framework - Sync & Async

ASP.NET MVC Framework - Sync & Async

LAVI

Sync & Async


圖片來源

同步 Sync 與非同步 Async

  • Sync 同步
    • 發起 I/O 請求的執行緒不從正在呼叫的 I/O 操作函式返回(被阻塞),直到 I/O 操作完成
  • Async 非同步
    • 發起 I/O 請求的執行緒不等 I/O 操作完成,就繼續執行隨後的程式,而 I/O 的結果再透過其他方式通知發起 I/O 請求的程式

簡單來說!

同步 Sync:
我有好多事要做,但一次只能做一件事,我要等這件事做完才能做其他

非同步 Async:
我有好多事要做,但有些事開始做以後要等他完成,我可以在這期間去做其他事,直到他做好後再回去處理他

Sync vs Async

已準備早餐為例,要吃的東西有:

  1. 咖啡
  2. 培根
  3. 吐司
  4. 柳橙汁

Sync

以同步 Sync 的例子來說,我們會這樣準備:

圖片來源

  1. 倒杯咖啡
  2. 加熱鍋子,發呆等著鍋子加熱
  3. 熱好鍋子後煎兩顆蛋,發呆等著蛋煎熟
  4. 煎完蛋後煎三片培根,發呆等著培根煎熟
  5. 把吐司放進吐司機加熱,發呆等著吐司熱
  6. 等吐司加熱好後,抹上果醬
  7. 倒杯果汁

但這樣我們會浪費好多時間在發呆等待,大約花了 30 分鐘準備早餐
可是在分秒必爭的早晨,這樣怎麼可以呢!

Async

如果站在程式非同步 Async 的角度來準備:

圖片來源

  1. 倒杯咖啡
  2. 加熱煎蛋的鍋子,等鍋子加熱的期間去做下一件事
  3. 加熱煎培根的鍋子,等鍋子加熱的期間去做下一件事
  4. 把吐司放進吐司機加熱,讓吐司慢慢加熱的同時,回去煎蛋
  5. 把蛋煎熟
  6. 把培根煎熟
  7. 吐司加熱好了,抹上果醬
  8. 倒杯果汁

如此一來,我們只花了 15 分鐘準備早餐,就可以多多賴床 15 分鐘了呢!

Sync or Async

一般程式碼是由上往下,一行一行程式碼同步 Sync 去執行的,乍看之下也沒什麼問題,甚至大部分的情境都是用同步的方式去撰寫程式

但若是處理需要等待、不確定需要多久才能完成的任務,ex 存取 I/O、外部資源、HTTP Request…等,就會需要使用非同步來處理了

使用情境

  1. JavaScript 用 setTimeout 倒數 10 秒後 alert 訊息,但在等待期間可以先做其他事
  2. JavaScript 發 HTTP Request 等待 Server 回傳資料的期間,可以先做其他事

Callback Function

而非同步中等待完成的任務,會呼叫 Callback Function 來處理接下來要做的事情

Callback Function 是一個被作為參數帶入另一個函式中的「函式」,而這個作為參數帶入的函式將在未來某個時間點被呼叫和執行

而在 AJAX 中也會使用到 CallbackFunction
關於什麼是 AJAX,可以參考我的這篇文章:ASP.NET MVC Framework - AJAX

這裡是一個範例:

1
2
3
4
5
6
7
8
9
10
11
12
13
$.ajax({
type: "POST",
url: "/Book/DeleteBook",
data: "Id=" + bookId,
dataType: "json",
success: function (response) {
var tr = $(this).closest('tr');
$("tr").remove();
},
error: function (error){
alert("系統發生錯誤");
}
})

這段程式碼其實是這樣運作的:
Server 刪除書本後,Response 回傳給 Client 端,接著才會執行 success 定義的 Callback Function,也就是從 UI 上的 Table 移除該筆資料

而 Callback Function 只會在非同步 Async 任務執行完成後才會觸發,也就是說,只要 Server 一直沒有 Response,那 Callback Function 就永遠不會被執行!

Sync & Async in JavaScript

JavaScript 是一種單執行緒的程式語言,所有的程式碼片段都會在 stack 中被執行
而且一次只會執行一個程式碼片段,也就是一次只做一件事

但這樣會導致在同步 Sync 狀態下,例如 setTimeout、Event Callback、HTTP Request、UI Render 這些需要時間,卻又不確定觸發執行時間的操作,光等他們就飽了?

於是可利用同步 Sync、非同步 Async 概念來處理這些事件,將他們以非同步的方式處理,待執行完同步的程式碼任務後,再來處理這些事件

Sync in JavaScript

但正如剛才提到,JavaScript 是單執行緒的程式語言,則該如何處理非同步事件呢?


圖片來源

  1. setTimeout、Event、AJAX、UI Render…等等操作,會先被拉出 stack 以避免阻塞
  2. 前一步驟中被拉出的任務執行完畢後,其 Callback Function 會被丟到工作佇列 Queue 中
  3. 等到同步 Sync 的程式碼執行完(Stack 清空),才會去撈在佇列 Queue 中的 Function 執行,這個過程稱為 Event Loop

Sync & Async mixed performance

AJAX

再來看看以下這段程式:

1
2
3
4
5
6
7
8
9
10
11
12
13
$.ajax({
type: "POST",
url: "/Book/DeleteBook",
data: "Id=" + bookId,
dataType: "json",
success: function (response) {
console.log("Server 已將資料刪除");
},
error: function (error) {
alert("系統發生錯誤");
}
});
console.log("想不到吧,是我先被印出");

他的執行順序是這樣:

  1. 一開始先執行的 AJAX 先被拉出 stack
  2. 印出 “想不到吧,是我先被印出”
  3. 等到 Server Response 後,Callback Function 被丟到工作佇列
  4. 同步執行的工作都完成後,撈出工作佇列的 Callback Function 執行
  5. 印出 “Server 已將資料刪除”

所以呀,無論 Server 處理速度多快,就算是瞬間回應,也不會先被印出
與此同理,Event 事件觸發、JavaScript 改變 UI…等,也是如此機制

addEventListener

再來看看這個例子:

1
2
3
4
5
6
7
8
9
10
11
12
document.body.addEventListener('click', function () {
console.log("Click!");
});

var now = Date.now();
while(true) {
if(Date.now() > now + 3000){
break;
}
}

console.log("Hello");

如果在 3 秒內觸發了 click Event,卻不會馬上印出 “Click!”
而是會等到 3 秒後
印出了 “Hello” 後,才會印出 “Click!”

innerHTML

那麼那麼,再來看看這個例子:

1
2
3
4
5
6
7
8
9
10
document.body.innerHTML = "";

var now = Date.now();
while(true) {
if(Date.now() > now + 3000) {
break;
}
}

console.log("Hello");

因為渲染 Render 的事件也是被丟到非同步處理,所以會等所有的同步程式碼執行完才會觸發
因此執行的順序是:
等待 3 秒 -> 印出 “Hello” -> document.body.innerHTML 清空畫面

Async VS Multithreading

如此一來,雖然感覺非同步 Async 和多執行緒 Multithreading 感覺很像,都能讓完成所有任務的總時長縮減,但本質上兩者大不相同

Async

  • 允許執行緒在等待時間先處理其他作業,透過消除閒置增加效率
  • 對於吃 CPU 的作業無法處理(改用多執行緒處理會更有效率)

Multithreading

  • 建立多執行緒,將多個任務交給不同執行緒個別處理,利用分工加速完成任務總時長
  • 對於等待 I/O 回應的工作幫助不大(改用非同步處理會更有效率)

Reference

  • Course 5 FrontEnd Advanced 課程(非公開)