Async in Dart (1) รู้จัก Isolates และ Event Loop กับการทำงานไม่ประสานเวลากัน
Tags: beginners, programming, dart, flutter
Published: 2020-04-25 14:01:42

---
canonical_url: https://www.centrilliontech.co.th/blog/2807/async-in-dart-1-isolates-event-loop/
---

ภาษา Dart มีฟีเจอร์สำหรับทำงานกับการเขียนโปรแกรมแบบ Asynchronous เต็มรูปแบบ แต่ก่อนจะสอนการใช้งาน เรามาเกริ่นว่า Synchronous และ Asynchronous Programming เป็นยังไง

## Synchronous และปัญหาการโหลดข้อมูล

ตามปกติแล้วเวลาเราเขียนโปรแกรม เราก็จะเขียนคำสั่งเรียงต่อๆ กัน แต่ในบางสถานการณ์เราอาจจะต้องมีการโหลดข้อมูลจาก **Data Source** ภายนอก เช่นอาจจะอ่านค่าจาก Database, โหลดข้อมูลผ่านเน็ตเวิร์คจาก API ซักตัวนึง

ถ้าเขียนโค้ดแบบธรรมดาเราก็จะได้อะไรประมาณนี้

```dart
print('start loading');
var data = loadData();
print(data);
print('done');
```
นั่นคือเริ่มด้วยการ `loadData()` แล้วพอได้ data มาก็เอามาแสดงผล

แล้วปัญหามันอยู่ตรงไหนกัน ??

เพราะการเชื่อมต่อ I/O หรือขอข้อมูลจากภายนอกจะเกิดสิ่งที่เรียกว่า **"Delay"** ขึ้นน่ะสิ!! เพราะการติดต่อกับแหล่งข้อมูลจากภายนอกจะต้องเสียเวลาในการเชื่อมต่อ แม้ไม่ได้มากนัก อาจจะแค่ไม่กี่ร้อย millisec แต่ก็ถือว่าเป็นระยะเวลาที่นานมากสำหรับ CPU

ถ้าเราเขียนโปรแกรมในโหมด Command Line การที่ข้อมูลจะตอบกลับมาช้าซัก 1-2 วินาที ก็คงไม่มีผลอะไรมาก (หน้าจอโปรแกรมก็ค้างรอโหลดอยู่ตรงนั้น พอโหลดเสร็จค่อยแสดงผลออกมา)

![](https://4.bp.blogspot.com/-SCuLQH9Xayo/XqK8Br7pk1I/AAAAAAAASR0/fzeLbR4Lsdwuu_bAZpJWjl5euAzUzTRMgCNcBGAsYHQ/s1600/skeleton400.gif)

แต่โปรแกรมหรือแอพที่เราใช้ๆ กันอยู่ทุกวันนี้มันไม่ได้เป็นแบบนั้น

ถ้าลองนึกถึงโปรแกรมในปัจจุบันส่วนใหญ่แล้ว จะมีUIให้ใช้งานแบบกราฟิกโหมด .. ก็คือใช้งานผ่านการคลิกเมาส์หรือการกดปุ่มโน่นนี่นั่นแหละ

นั่นแปลว่านอกจากโปรแกรมจะต้องทำการโหลดข้อมูลแล้ว มันยังต้องควบคุมส่วนUIด้วย ทีนี้ลองคิดดูว่าแต่ถ้าเราเขียนโปรแกรมแบบเดิมๆ กับโปรแกรมที่มีUIเป็นกราฟิกโหมดจะเกิดอะไรขึ้น

![](https://3.bp.blogspot.com/-CCyaGM-newo/XqK9fNbqeXI/AAAAAAAASSA/MDQV9bCezfMid3m7iUb0cZ0htk6ANcE5ACNcBGAsYHQ/s1600/skeleton-loading-1.gif)

คำตอบคือ .. จังหวะที่โปรแกรมเราสั่งโหลดข้อมูล **และมันกำลังรอให้ข้อมูลตอบกลับมา** อยู่นั้น CPUจะไม่สามารถปลีกตัวไปทำงานอย่างอื่นได้เลย แม้แต่การจัดการกับUI (แปลว่าโปรแกรมเราจะดูเหมือนค้างไปเลย กดปุ่มอะไรก็ไม่ได้ ถ้าเป็นแอพในมือถือก็จะสกอร์หน้าขึ้นลงอะไรไม่ได้เลย ค้างอยู่เฉยๆ แบบนั้นเลย) โปรแกรมจะกลับมาทำงานต่อได้อีกทีก็ต้องโน้นเลย คำสั่งที่ทำการโหลดข้อมูลทำงานเสร็จแล้วนั่นแหละ!





## Asynchronous โปรแกรมที่ทำงานกระโดดไปกระโดดมา
ความจริงแล้ว Asynchronous แปลว่าการทำงานแบบไม่ประสานเวลา (ตรงข้ามกับ Synchronous ที่แปลว่าการทำงานแบบประสานเวลา)

นั่นแปลว่าโค้ดที่เราเขียนอาจจะไม่ได้รันตามลำดับบรรทัดก็เป็นได้ เช่นตัวอย่างข้างล่างนี่

```dart
// Synchronous
void main(){
    print('start loading');
    var data = loadData();
    print(data);
    print('done');
}

// Asynchronous
void main(){
    print('start loading');
    loadData().then((data){
        print(data);
    });
    print('done');
}
```

![](https://1.bp.blogspot.com/-zOGSV32RGT8/Xoy2tQ55uiI/AAAAAAAASPQ/Z04vZ8zneEgtj7tjH_j3jrO-1N3Aj079gCNcBGAsYHQ/s1600/dart-async-1-01.png)

จากโค้ด จะเห็นว่าสำหรับฟังก์ชัน `loadData()` ที่มีการทำงานค่อนข้างนาน เราจะไม่หยุดรอผลลัพธ์จากมัน **แต่จะวางฟังก์ชันอีกตัวเอาไว้บอกว่า เมื่อdataกลับมา จะให้ทำอะไรต่อไป** หรือเรารู้จักกันในชื่อ **"Callback Function"** นั่นเอง ซึ่งสามารถกำหนดได้ด้วยคำสั่ง `.then()` นะ

*จากโค้ดด้านบน เมื่อฟังก์ชัน `loadData()` ทำงานเสร็จ มันจะข้ามไปทำงานบรรทัดต่อไปซึ่งก็คือ `print("done")` ต่อทันทีโดยไม่ต้องรอให้ `data` กลับมาก่อน*








## Asynchronous Programming ฉบับภาษาDart!



### Isolates

ในภาษาDart, โค้ดทั้งหมดจะถูกรันอยู่ในสิ่งที่เรียกว่า "Isolates" ซึ่งเป็นเหมือนพื้นที่เล็กๆ บนเมโมรี่ ที่โปรแกรมหรือ Isolates ภายนอกไม่สามารถเข้ามาได้ (private) 

ในภาษาอื่นๆ เช่น c/c++, Java จะสามารถสร้าง Thread ขึ้นมาได้ (ซึ่งส่วนใหญ่แล้วจะต้องควบคุมเทรดพวกนี้ด้วยตัวเอง)

### อธิบาย Thread แบบรีบๆ

*สโคปเนื้อหาในบทความนี้ไม่ได้รวมเรื่อง Thread แต่จะพออธิบายแบบสั้นๆ สำหรับคนยังไม่รู้จักมัน*

Thread เป็นเหมือน**โปรแกรมย่อย**ในโปรแกรมหลักของเราอีกทีนึง ในภาษาส่วนใหญ่จะมีฟีเจอร์ให้เราแยกโค้ดของเราออกเป็นโปรแกรมย่อยๆ แล้วรันมันพร้อมกันได้อยู่แล้ว โดยโปรแกรมหลักของเราจะถูกรันอยู่บนสิ่งที่เรียกว่า **"Main Thread"** (หรือเรียกว่า **"UI Thread"**) เสมอ เป็นเทรดตัวหลักที่เอาไว้ควบคุมการแสดงผลบนUI แต่ถ้าเราต้องการเชื่อมต่อ Data Source ภายนอกที่กินเวลาค่อนข้างนานเราจะเป็นต้องสร้างเทรดตัวใหม่ขึ้นมา (เป็น Background Thread) แล้วโยนงานโหลดข้อมูลให้เทรดตัวนี้ไปทำงานแทน, UI Thread จะได้จัดการงานแสดงผลให้ผู้ใช้ต่อไปได้โดยหน้าจอไม่ค้าง .. ต่อมาเมื่อ Background Thread ตัวนั้นโหลดข้อมูลเสร็จแล้ว ก็จะส่งข้อมูลตัวนั้นกลับมาให้ Main Thread เพื่อแสดงผลขึ้นบนหน้าจอหรืออะไรต่อไป

> ส่วนงานแบบไหนถือว่าเป็นงานที่ยาวจนทำให้ UI ค้างได้ไม่ได้มีกำหนดตายตัว เช่นใน Android กำหนดไว้ว่าUI ควรจะเรนเดอร์ที่อัตรา 60Hz นั่นแปลว่างานๆ หนึ่งต้องทำงานให้เสร็จใน 16.67ms เท่านั้น ถ้าเกินกว่านั้นควรแยกไปทำงานในเทรดตัวใหม่แทน


## Event Loop

สำหรับภาษา Dart ถูกออกแบบมาให้ใช้งานง๊าย~ง่าย อยากเขียนAsyncงั้นเหรอ ไม่ต้องสร้างThreadอะไรเองแบบแมนนวลหรอก เราจัดให้!

ภาษา Dart นั้นจะแบ่งโค้ดของเราเป็น Isolates แยกๆ กันให้ด้วยตัวเองโดยโปรแกรมเมอร์ไม่ต้องกำหนดอะไรเลย

ทีนี้, เมื่อโค้ดของเราถูกแบ่งออกเป็นหลายๆ Isolates แล้วมันจะจัดลำดับการทำงานของโค้ดยังไงล่ะ 

คำตอบคือภาษา Dart นั้นมีสิ่งที่เรียกว่า **"Event Loop"** จัดการอยู่เบื้องหลัง (ใช้เขียน JavaScript น่าจะคุ้นๆ กับสิ่งนี้เพราะแนวคิดมันเหมือนกันเลย)

![](https://1.bp.blogspot.com/-QJ5JBIQ2tGQ/Xoy2tT3RlTI/AAAAAAAASPM/Pn0Zfe-iX_g_Y_lUaVxNULPS40vjmFI3QCNcBGAsYHQ/s1600/dart-async-1-02.png)

Event Loop นั้นเหมือนกับผู้ที่หยิบโค้ดDartของเราไปรันแบบทีละตัวๆ แต่เพราะภาษาDartสามารถสร้าง Isolates ออกมาเรื่อยๆ กี่ตัวก็ได้

แต่เพราะ Event Loop นั้นมีอยู่แค่ตัวเดียว Isolates ที่มีอยู่หลายตัวก็เลยต้องรอกันอยู่ในคิว รอให้ Event Loop ค่อยๆ หยิบไปรันทีละตัว

## แล้วDartมีหลักการแยกโค้ดออกเป็น Isolates ส่งเข้า Event Loop ยังไงนะ

ในภาษาDartจะมีคำสั่งมากมาย ทุกคำสั่งจะมีการทำงานแบบ Synchronous ทั้งหมด ยกเว้นคลาสที่ชื่อว่า `Future` และ `Stream` ที่เมื่อมันทำงานปุ๊บ มันจะกลายเป็น Asynchronous ทันที

*(ส่วน Future และ Stream ใช้งานยังไง เดี๋ยวเราจะพูดกันในบทต่อๆ ไป)*

**ฟังก์ชันไหนรีเทิร์นค่ากลับมาเป็น `Future` หรือ `Stream` จะถูกแยกออกไปอยู่ใน Isolates ตัวใหม่ทันที**

ลองมาดูตัวอย่างกัน

สมมุติว่าเรามีฟังก์ชันที่รีเทิร์นค่า Future ชื่อ `future()` นะ 

```dart
void main(){
    print('main-1');

    future().then((){
        print('future A');
    });

    print('main-2');

    future().then((){
        print('future B-1');
        future().then((){
            print('future C');
        });
        print('future B-2');
    });

    print('main-3');
}

```

จากตัวอย่างข้างบน เรามีฟังก์ชันหลักคือ `main` ที่มีคำสั่งจำนวนหนึ่งอยู่ข้างใน แต่ให้ลองสังเกตฟังก์ชัน `future()` ทั้ง 2 ตัว, ซึ่ง `future()` ทั้งสองตัวนี้จะถูกรันเมื่อฟังก์ชัน `main` ทำงานนั่นแหละ แต่**ผลของ `future()` ที่เขียนอยู่ใน `.then()` นั้นจะยังไม่ถูกทำงานในตอนนั้น แต่จะถูกแยกออกไปเป็น Isolates ตัวใหม่ เข้าคิวไว้รออยู่ให้ Event Loop มาหยิบไปรัน**

![](https://3.bp.blogspot.com/-iQQISvnN7kA/XqMj10Df5qI/AAAAAAAASSM/KrbB4NARJ0ggO7_-7lc-DrQuMXnYY2zRwCNcBGAsYHQ/s1600/capture-1.gif)

ก็จะได้ output เรียงตามลำดับนี้
```
main-1
main-2
main-3
future A
future B-1
future B-2
future C
```

## ตอนต่อไป
ในบทต่อไปเราจะมาพูดกันต่อว่าถ้าเราต้องการสร้างโค้ดที่เป็น Async เราต้องจะเขียนยังไง กับตัวที่ง่ายที่สุดนั่นคือ **Future** หรือพูดง่ายๆ คือ "เดี๋ยวจะทำงานโค้ดนี้ในอนาคต"