สรุปหลักการทำงานของ Kotlin Coroutines เปรียบเทียบกับ Java Thread และ Use case ที่จำเป็นสำหรับการพัฒนา Android Application ในปัจจุบัน เน้นความเข้าใจเรื่อง Concurrency และ Thread Management
1. The Fundamental Problem: Why Concurrency?
โดยธรรมชาติ โปรแกรมทำงานแบบ Sequential (ทำทีละบรรทัด) แต่ Android มีกฎเหล็กคือ “ห้ามบล็อก Main Thread” (เส้นเลือดใหญ่ของ App)
- Main Thread มีหน้าที่แค่วาด UI และรับ Touch Event
- ถ้าเราสั่ง Main Thread ให้ไปโหลดไฟล์ใหญ่ๆ (Long Running Task) -> Main Thread จะหยุดวาดหน้าจอ -> App ค้าง (ANR)
ทางแก้: เราต้องสร้าง Worker Thread แยกออกมาทำเพื่อรับงานหนักไปทำคู่ขนาน (Parallel)
2. The Old Solution: Java Threads
สมัยก่อนเราใช้ new Thread() ซึ่งเป็นการสร้าง Thread จริงๆ ระดับ OS แบบ 1:1
ข้อเสีย (Pain Points):
- Expensive: การสร้าง Thread กิน Memory เยอะ (Stack Size ~1MB/Thread)
- Context Switching: การสลับ Thread ไปมาใช้ CPU เยอะ
- Blocking Model: นี่คือปัญหาใหญ่สุดครับ
Visualizing Java Thread Blocking: เมื่อ Java Thread สั่งงาน I/O (เช่น Network request) มันจะหยุดนิ่ง (Blocked) และไม่คืน Resource ให้ระบบ
[ CPU ]
|
/---+---\
| |
[Thread A] [Thread B]
| |
X |
(BLOCKED) (RUNNING)
Waiting
for I/O
จากภาพ: เราเสีย Thread A ไปฟรีๆ เพื่อให้นั่งรอของเฉยๆ โดยไม่ได้ประมวลผลอะไร
3. The New Solution: Kotlin Coroutines
Coroutines คือ “Lightweight Thread” ที่ออกแบบมาเพื่อแก้ปัญหาข้างต้น
- Not 1:1 Mapping: Coroutine ไม่ใช่ OS Thread โดยตรง (Coroutine เป็นหมื่นตัว รันบน Thread จริงไม่กี่ตัวได้)
- Suspension vs Blocking: หัวใจสำคัญอยู่ที่การใช้ทรัพยากรครับ
Comparison: Blocking vs Suspension
Blocking (Java Thread) Thread ถูกล็อคไว้จนกว่างานจะเสร็จ ไม่สามารถนำไปประมวลผลงานอื่นได้
Time -------->
[Thread] [Work A] ---- [ WAITING (Blocked) ] ---- [Finish A]
( ❌ Resource Wasted )
Suspension (Kotlin Coroutines)
เมื่อเจอคำสั่ง suspend (เช่น รอ Network) Coroutine จะ “หยุดพัก” แล้ว “คืน Thread” ให้ไปทำงานอื่น (Work B) ก่อน พอของมาส่ง ค่อยกลับมาทำต่อ
Time -------->
[Thread] [Work A] --(Suspend)--> [Work B] --(Resume)--> [Finish A]
( ✅ Resource Utilized )
The Magic of “Suspend”
suspend คือ keyword ที่บอก Compiler ว่าฟังก์ชันนี้สามารถ “หยุดพักชั่วคราว & คืน Thread” ได้
โดย Compiler จะแปลง Code เราเป็น State Machine เพื่อเซฟสถานะเก็บไว้ (Checkpoints) รอวันกลับมา Resume นั่นเอง
4. Managing Threads: Dispatchers & Thread Pools
เมื่อ Coroutine ต้องทำงาน มันต้องการ “ที่อยู่” (Thread) Kotlin จัดการสิ่งนี้ผ่าน Dispatchers ซึ่งเป็นตัวแบ่งกลุ่ม Thread Pool ตามประเภทงาน
1. Dispatchers.Main
- Thread: มีเส้นเดียว (UI Thread)
- Job: งานเบาๆ, Update UI, Animation
- Warning: ห้ามทำงานหนักตรงนี้เด็ดขาด
2. Dispatchers.IO
- Thread Pool: ขนาดใหญ่ (64+)
- Job: งานที่ต้อง “รอ” นานๆ เช่น Network (API), Database room, Read Files
- Why: เพราะงานพวกนี้ Thread ไม่ได้ใช้ CPU (แค่นั่งรอของ) เลยเปิด Thread รอได้เยอะๆ
3. Dispatchers.Default
- Thread Pool: ขนาดเท่าจำนวน CPU Cores (e.g. 8 threads)
- Job: งานคำนวณหนักๆ (CPU Intensive) เช่น Image Processing, Sorting Algo, JSON Parsing
- Why: ถ้ามีสมองแค่ 8 หัว จ้างคนมา 100 คนก็ทำงานได้ทีละ 8 คนอยู่ดี สู้จ้างมาแค่ 8 คนทำงานเต็มที่ไปเลยดีกว่า (ลด Context Switching)
4. Others
- Dispatchers.Unconfined: ไม่จำกัด Thread (เริ่มที่ไหน จบที่นั่น หรือเปลี่ยนไปเรื่อย) มักใช้ใน Testing
5. Experience: Coding in Action
เปรียบเทียบความแตกต่างระหว่างวิธีเก่า vs วิธีใหม่
The Old Way: Callback Hell (Java) โค้ดจะบุ๋มลึกลงไปเรื่อยๆ อ่านยาก จัดการ Error ยาก
api.login(user, pass, new Callback<Token>() {
@Override
public void onResponse(Token token) {
api.getProfile(token, new Callback<Profile>() {
// ... Code ซ้อนกันเป็นปีระมิด ...
});
}
});
The Modern Way: Sequential Style (Kotlin)
เราจะใช้ viewModelScope ซึ่งเป็นพระเอกในการจัดการ Lifecycle ของ Android
// 1. Syntax "trailing lambda" ทำให้เขียน launch { } ได้เลย
viewModelScope.launch {
try {
// 2. Suspend function: หยุดรอแบบไม่ Block UI
val token = api.login(user, pass)
// 3. เอา token ไปใช้ต่อได้เลย (ไม่ต้องซ้อน callback)
val profile = api.getProfile(token)
// 4. กลับมา update UI ที่ Main Thread อัตโนมัติ
updateUI(profile)
} catch (e: Exception) {
showError(e)
}
}
Why is this code so clean?
viewModelScope(The Guard):- เป็น Coroutine Scope ที่ผูกกับอายุขัยของ
ViewModel - Auto-Cancel: ถ้าผู้ใช้ออกจากหน้าจอนี้ (
onDestroy) งานที่ทำค้างไว้ในนี้จะถูกยกเลิกให้อัตโนมัติ (ไม่ต้องกลัว Memory Leak)
- เป็น Coroutine Scope ที่ผูกกับอายุขัยของ
Sequential Logic:
- เราเขียนโค้ดบรรทัดต่อบรรทัดได้เลย ไม่ต้องมี Callback ซ้อนๆ กัน
try-catchสามารถดัก Error ได้ทั้งก้อน (รวมถึง Error ที่เกิดจาก Network/Async) ซึ่งทำไม่ได้ใน Java Callback ปกติ
Trailing Lambda Syntax:
- ทำไมเขียน
launch { ... }ได้เลยโดยไม่ต้องมีวงเล็บ()? - ใน Kotlin ถ้า parameter ตัวสุดท้ายเป็นฟังก์ชัน (Lambda) เราสามารถย้ายปีกกา
{}ออกมาข้างนอกวงเล็บได้ ทำให้โค้ดดูสะอาดเหมือนภาษาพูด
- ทำไมเขียน
6. The Caveat: Thread Safety
ถึงจะใช้ง่าย แต่ Coroutines ก็ยังมีปัญหา Race Condition ได้ เหมือน Java Thread ปกติครับ
The Problem: แย่งกันใช้ทรัพยากร ยกตัวอย่าง “สามี-ภรรยา แย่งกันกด ATM บัญชีเดียวกัน” ถ้ากดพร้อมกันแล้วระบบไม่ป้องกัน (Not Thread-safe) เงินอาจจะหายหรือติดลบได้
Visualizing the Solution: Synchronized vs Mutex
- Synchronized (Blocking Mode): เมื่อ Thread B ต้องการกุญแจที่ Thread A ถืออยู่… Thread B จะต้อง “ยืนรอเฉยๆ” จนกว่า A จะเสร็จ (เสีย Thread ไปฟรีๆ)
Time -------->
[Thread A] [ Holds Lock ] ------------------> [ Release ]
[Thread B] [ WAITING (Blocked) ] ----------> [ Get Lock ]
( ❌ Thread Frozen )
- Mutex (Suspending Mode): เมื่อ Coroutine B ต้องการกุญแจที่ไม่ว่าง… มันจะ “พักงาน (Suspend)” และคืน Thread ให้ระบบเอาไปรันงานอื่น (Work C) รอพลางๆ ได้
Time -------->
[Coroutine A] [ Holds Mutex ] ------------------------------> [ Release ]
[Coroutine B] [ Suspend ] ....................> [ Resume ]
[Thread] [ Run A ... ] [ Run Work C... ] [ Run Work D ] [ Run B... ]
( ✅ Thread Busy/Useful )
Code Example:
val mutex = Mutex() // สร้างกุญแจ
launch {
// ใช้ withLock แทน synchronized
mutex.withLock {
counter++ // ปลอดภัยและไม่ Block Thread
}
}
Summary Cheat Sheet
| Feature | Java Threads | Kotlin Coroutines |
|---|---|---|
| Model | 1 Thread = 1 OS Thread (Heavy) | Many Coroutines = 1 OS Thread (Light) |
| Waiting | Blocking (Waste Resource) | Suspension (Save Resource) |
| Code Style | Callback Hell | Sequential / Imperative |
| Safety | Synchronized (Blocking) | Mutex (Suspending) |
| Management | Manual (Hard) | Structured Concurrency (Easy via Scopes) |