Async in Dart (1) รู้จัก Isolates และ Event Loop กับการทำงานไม่ประสานเวลากัน

Blog-web_02-1
This entry is part 1 of 5 in the series Async-in-Dart-Series

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

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

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

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

print('start loading');
var data = loadData();
print(data);
print('done');

นั่นคือเริ่มด้วยการ loadData() แล้วพอได้ data มาก็เอามาแสดงผล

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

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

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

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

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

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

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

Asynchronous โปรแกรมที่ทำงานกระโดดไปกระโดดมา

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

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

// 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');
}

จากโค้ด จะเห็นว่าสำหรับฟังก์ชัน 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 น่าจะคุ้นๆ กับสิ่งนี้เพราะแนวคิดมันเหมือนกันเลย)

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() นะ

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 มาหยิบไปรัน

ก็จะได้ output เรียงตามลำดับนี้

main-1
main-2
main-3
future A
future B-1
future B-2
future C

ตอนต่อไป

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

Series NavigationAsync in Dart (2) เดี๋ยวจะตอบให้ในอนาคต กับการใช้ Future >>
Share on facebook
Facebook
Share on google
Google+
Share on twitter
Twitter
Share on linkedin
LinkedIn
Share on pinterest
Pinterest

Recent Post

ปี2020, จริงๆ เราไม่ต้องใช้ jQuery แล้วก็ได้นะ

jQuery เป็นหนึ่งใน JavaScript Library ที่โด่งดังมาก (เมื่อ 10 ปีที่แล้ว) เรียกว่าในยุคนั้นแทบจะทุกเว็บจะต้องมีการติดตั้ง jQuery เอาไว้อย่างแน่นอน แต่เมื่อยุคสมัยเปลี่ยนไป เบราเซอร์ใหม่ๆ ไม่มีปัญหาการรัน JavaScript ตั้งแต่ ES6 ขึ้นไปแล้ว การใช้งาน jQuery จึงลดน้อยลงเรื่อยๆ แต่ก็มีบางโปรเจคเหมือนกัน ที่เราต้องเข้าไปแก้โค้ดเก่าๆ ซึ่งเขียนด้วย jQuery เอาไว้ ในบทความนี้จะมาเปรียบเทียบว่าทุกวันนี้เราสามารถแปลง jQuery ให้กลายเป็น Vanilla

Vue3 มีอะไรเปลี่ยนแปลงไปบ้าง

Vue 3 เพิ่งเปิดตัวมาเมื่อเดือนที่แล้ว ซึ่งมาพร้อมกับฟีเจอร์ใหม่ๆ และสิ่งที่เปลี่ยนแปลงไป เรามาดูกันดีกว่า เขียนใหม่ด้วย TypeScript ภาษา JavaScript นั้นไม่มี Type ของตัวแปรทำให้เวลาเขียนโปรแกรมมีโอกาสเกิดข้อผิลพลาดเยอะ ดังนั้นการเขียนงานโปรเจคใหญ่ๆ คนเลยนิยมเปลี่ยนไปใช้ TypeScript แทน (ถ้ายังไม่รู้จัก TypeScript อ่านต่อได้ที่นี่) สำหรับ Vue 3.0 นี้ก็เป็นการเขียนใหม่ด้วย TypeScript แทน แต่เวลาเราเอามาใช้งาน เราสามารถเลือกได้ว่าจะใช้แบบ JavaScript ตามปกติ

สอนใช้ TypeScript ในโปรเจค Node.js + Express

Node.js กับ TypeScript Node.js เป็นหนึ่งในเฟรมเวิร์คยอดนิยมสำหรับเขียนโปรแกรมฝั่ง Backend แต่เพราะมันสร้างมาตั้งแต่ปี 2009 ยุคที่ JavaScript ยังเป็นเวอร์ชัน ES5 อยู่เลย โดยดีฟอลต์แล้ว Node.js เลยไม่ซัพพอร์ท TypeScript เลย ไม่เหมือนกับ Deno ที่เพิ่งสร้างขึ้นมาโดยซัพพอร์ท TypeScript เป็นค่า default ตั้งแต่แรก เพื่อชีวิตที่ดีกว่า มาเซ็ตโปรเจค Node.js + Express

UX UI Design เบื้องต้น Section 1 “พื้นฐาน UX UI”

จากที่เราได้รู้จักโลกของ UX UI ไปใน Section ที่แล้ว ใน Section นี้ เราจะมาเข้าใจให้ลึกขึ้นอีกนิด สำหรับ UX UI Design โดยมีคำกล่าวสั้นๆว่า UX คือ นามธรรม UI คือ รูปธรรม และในพื้นฐานแรก ที่เราจะมาเริ่มเรียนรู้คือ UI Design เคลียร์กันชัดๆ UI Design คืออะไร? นิยามของ

ทำยังไง? อยากให้ JavaScript เรียกฟังก์ชันในภาษา PHP เขียนโค้ดยังไงนะ

หนึ่งในคำถามที่เว็บโปรแกรมเมอร์มือใหม่ถามกันเยอะมากจนน่าจัดเก็บไว้เป็น FAQ เลยก็คือ "จะทำยังไงให้เราเรียกใช้ฟังก์ชันภาษา PHP จากสคริป JavaScript ได้" เช่นแบบนี้... <button onclick="<?php functionInPhp1(); ?>"> คลิกฉันสิ! </button> หรือแบบนี้... function functionInJs(){ let x = <?php functionInPhp2(); ?> } คำตอบคือ ด้วยการทำงานของเว็บที่ทำงานบนโปรโตคอล http นั้น...มันทำไม่ได้!! (แต่มีวิธีแก้

[How To] การติดตั้ง Google Maps for Flutter เบื้องต้น

วันนี้ผมจะมาแนะนำและวิธีการใช้งานเบื้องต้นของ plugin ที่น่าสนใจตัวหนึ่งที่มีชื่อว่า "Google Maps for Flutter" โดย plugin ตัวนี้จะให้ Google Maps ที่เป็น Widget มาให้เราได้เปิดใช้งานแผนที่ของกูเกิ้ล ขั้นตอนการติดตั้ง อันดับแรก เราต้องทำการขอ API Key ที่ลิ้งค์ https://cloud.google.com/maps-platform/ เมื่อเข้ามาหน้าเว็บไซต์แล้วให้เข้าไปที่ Console (ตรงขวามุมบนของหน้าจอ) สร้าง Project ของเราขึ้นมาก่อน เมื่อทำการสร้างเสร็จแล้วให้เปิดแท็บด้านขวามือ แล้วเลือกเมนูที่ชื่อว่า

UX UI Design เบื้องต้น Section 0 “โลกของ UX UI”

หากเปรียบเทียบการออกแบบ UX UI เป็นประตู ดังนั้น Designer คือผู้ที่ต้องแก้ไขปัญหา เพื่อให้ตอบโจทย์ผู้ใช้ ไม่ใช่คำนึงแค่ความสวยงามเท่านั้น โดยใน Sec.0 นี้ เราจะมองภาพง่ายๆแบบใกล้ตัว เช่น การออกแบบประตู ซึ่งการออกแบบ เราจะมีพื้นฐานด้วยกัน 4 อย่าง ดังนี้ พื้นฐานการออกแบบมี 4 อย่าง คือ การออกแบบหน้าตา (User Interface Design) :: หน้าตาของประตู

สอนวิธีเซ็ตโปรเจค TypeScript / มาใส่ไทป์ให้ JavaScript เพื่อลดความผิดพลาดในการเขียนโค้ดกันดีกว่า

ภาษา JavaScript นั้นเป็นภาษาที่เริ่มเขียนได้ไม่ยาก ยิ่งถ้าใครเขียนภาษาสาย Structure/OOP เช่น C/C++, C#, Java มาก่อน อาจจะชอบในความเป็น Dynamic Type เราสร้างตัวแปรแล้วเก็บค่าอะไรก็ได้ ทำให้มีความคล่องตัวในการเขียนมากๆ ก่อนที่พอเขียนไปเรื่อยๆ เราจะพบความแปลกของมัน ยิ่งเขียนไปนานๆ เราก็พบว่ามันเป็นภาษาที่ทำให้เกิดบั๊กได้ง่ายมาก และเป็นหนึ่งในภาษาที่น่าปวดหัวที่สุดที่คนโหวตๆ กันเลย ด้วยเหตุผลแรกคือการที่ตัวแปรไม่มี Type นั่นเอง (อีกเหตุผลหนึ่งคือส่วนใหญ่ของคนที่เปลี่ยนมาเขียน JavaScript เคยชินกับภาษาแนว OOP มาก่อน พอมาเขียนภาษาแนว

รู้จักกับ TypeScript – ประวัติของภาษาที่เติมไทป์ให้กับ JavaScript

ในบทความนี้จะเล่าถึงที่มาที่ไปของ TypeScript อย่างเดียวนะ ส่วนเรื่องสอนว่าใช้งานยังไงได้บ้าง จะเขียนอีกทีในบล็อกต่อๆ ไป สำหรับภาษาโปรแกรมทุกวันนี้ ถ้าแบ่งคร่าวๆ ด้วยชนิดของตัวแปร เราจะแบ่งได้ 2 อย่าง คือ Static Type: ต้องกำหนดชนิดตัวแปร เช่น int, string ตั้งแต่สร้างตัวแปร และ Dynamic Type ตัวแปรประเภทนี้ไม่ต้องบอกว่าเก็บค่าชนิดไหน เปลี่ยนไปได้เรื่อยๆ สำหรับภาษาแบบ Dynamic Type ที่ไม่ต้องกำหนดชนิดตัวแปรให้แน่นอน จะเซ็ตค่าเป็นอะไรก็ได้นั้นอาจจะทำให้เขียนง่าย

Async in Dart (5) รู้จักกับ FutureBuilder/StreamBuilder, ตัวช่วยสร้าง Async-Widget ใน Futter

เนื้อหาในบทนี้ เป็นคลาสเฉพาะที่มากับ Flutter Framework เท่านั้น .. ถ้าเขียน Dart ธรรมดาจะไม่มีให้ใช้นะ!! เอาจริงๆ 99% ของคนที่ศึกษาภาษา Dart ก็เพื่อเอาไปเขียนแอพ (หรือเว็บ/เด็กส์ท็อป) แบบ cross-platform ด้วยเฟรมเวิร์ก Flutter นั่นแหละ ตามความคิดของเรา จริงๆ Flutter น่าจะเลือกภาษา Kotlin มาใช้แทนมากกว่า แต่ก็มีเหตุผลหลายๆ อย่างนั่นแหละที่ทำให้ทำไม่ได้ สำหรับการเขียน Flutter

Dependency Injection กับ Service Locator ต่างกันยังไงนะ?

ในโลกของ OOP เรามักจะสร้างคลาสต่างๆ ที่มีการเรียกใช้กันต่อเป็นทอดๆ มากมาย เช่นโค้ดข้างล่างนี่ class Car { private Engine engine; public Car() { this.engine = new Engine(); } } Car car = new Car(); เราสร้างคลาส Car และ Engine

Async in Dart (4) ควบคุมข้อมูลในstreamอย่างเหนือชั้นด้วย StreamController

Async in Dart (4) ควบคุมข้อมูลในstreamอย่างเหนือชั้นด้วย StreamController ในบทที่แล้วเราสอนการสร้าง Stream แบบง่ายๆ ไปแล้ว แต่ในบางครั้งเราต้องการควบคุมข้อมูลใน Stream แบบคัสตอมมากๆ ซึ่งข้อมูลอาจจะเข้ามาจากหลายทางมากๆ การที่เรามีแค่ yield จากฟังก์ชันเดียวอาจจะไม่เพียงพอ ดังนั้นเลยเป็นที่มาของคลาสที่ชื่อว่า StreamController ซึ่งเป็นคลาสที่เอาไว้ควบคุม Stream อีกที StreamController การสร้าง Stream แบบธรรมดาก็จะเป็นประมาณนี้ Stream<int> getNumberStream() async* {