Coroutines Kotlin คืออะไร
Kotlin Coroutines เป็นฟีเจอร์สำหรับ asynchronous programming ใน Kotlin ที่ช่วยให้เขียน concurrent code ได้ง่ายและอ่านง่ายเหมือนเขียน sequential code ปกติ Coroutines เบากว่า threads มาก (สร้างได้เป็นล้านตัวโดยไม่กิน memory เยอะ) ใช้งานกว้างขวางใน Android development, backend (Ktor, Spring), และ multiplatform บทความนี้อธิบาย Coroutines ตั้งแต่พื้นฐานจนถึงขั้นสูง พร้อมตัวอย่าง code, patterns ที่ใช้บ่อย และเปรียบเทียบกับ threading แบบดั้งเดิม
Coroutines พื้นฐาน
# coroutines_basics.kt — Kotlin Coroutines basics
import kotlinx.coroutines.*
// 1. launch — Fire and forget
fun main() = runBlocking {
println("Start: ")
// launch สร้าง coroutine ใหม่ — ไม่ block
val job = launch {
delay(1000) // suspend function — ไม่ block thread
println("Coroutine 1: ")
}
// async — return ค่าได้ (Deferred)
val deferred = async {
delay(500)
println("Coroutine 2: ")
42 // return value
}
println("Waiting...")
val result = deferred.await() // รอผลลัพธ์
println("Result: $result")
job.join() // รอให้ job เสร็จ
println("Done!")
}
// Output:
// Start: main
// Waiting...
// Coroutine 2: main
// Result: 42
// Coroutine 1: main
// Done!
Coroutine Builders & Scope
# builders.kt — Coroutine builders and scope
import kotlinx.coroutines.*
// === Coroutine Builders ===
// 1. runBlocking — Block current thread (ใช้ใน main/tests)
fun main() = runBlocking {
// code ข้างในเป็น coroutine scope
}
// 2. launch — Fire and forget (Job)
fun launchExample() = runBlocking {
val job: Job = launch {
delay(100)
println("launched!")
}
job.join()
}
// 3. async — Return value (Deferred<T>)
fun asyncExample() = runBlocking {
val deferred: Deferred<Int> = async {
delay(100)
42
}
val result = deferred.await()
println("Result: $result")
}
// 4. coroutineScope — Structured concurrency
suspend fun fetchData(): List<String> = coroutineScope {
val users = async { fetchUsers() } // parallel
val posts = async { fetchPosts() } // parallel
users.await() + posts.await() // wait both
}
// === Dispatchers ===
fun dispatcherExample() = runBlocking {
// Default — CPU-intensive (thread pool = CPU cores)
launch(Dispatchers.Default) {
println("Default: ")
}
// IO — I/O operations (thread pool = 64 threads)
launch(Dispatchers.IO) {
println("IO: ")
}
// Main — UI thread (Android)
// launch(Dispatchers.Main) { updateUI() }
// Unconfined — starts in caller thread, resumes in any
launch(Dispatchers.Unconfined) {
println("Unconfined: ")
}
}
// === Structured Concurrency ===
// Parent coroutine รอ children ทั้งหมดก่อนจบ
// ถ้า child fail → parent cancel children ที่เหลือ
suspend fun structuredExample() = coroutineScope {
launch { task1() }
launch { task2() }
launch { task3() }
// coroutineScope จะรอทั้ง 3 tasks จบก่อน return
}
Patterns ที่ใช้บ่อย
# patterns.kt — Common coroutine patterns
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
// === Pattern 1: Parallel Decomposition ===
suspend fun fetchUserProfile(userId: String): UserProfile = coroutineScope {
val user = async { userService.getUser(userId) }
val posts = async { postService.getUserPosts(userId) }
val followers = async { socialService.getFollowers(userId) }
UserProfile(
user = user.await(),
posts = posts.await(),
followers = followers.await()
)
}
// === Pattern 2: Timeout ===
suspend fun fetchWithTimeout(): String {
return withTimeoutOrNull(3000) { // 3 seconds timeout
fetchSlowApi()
} ?: "Timeout — ใช้ default value"
}
// === Pattern 3: Retry ===
suspend fun <T> retry(
times: Int = 3,
initialDelay: Long = 100,
maxDelay: Long = 5000,
factor: Double = 2.0,
block: suspend () -> T
): T {
var currentDelay = initialDelay
repeat(times - 1) {
try {
return block()
} catch (e: Exception) {
println("Retry /$times after ms")
}
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
}
return block() // last attempt
}
// === Pattern 4: Flow (Reactive Streams) ===
fun numberFlow(): Flow<Int> = flow {
for (i in 1..10) {
delay(100)
emit(i) // ส่งค่าทีละตัว
}
}
suspend fun flowExample() {
numberFlow()
.filter { it % 2 == 0 }
.map { it * it }
.collect { println("Received: $it") }
}
// === Pattern 5: Channel (Producer-Consumer) ===
fun CoroutineScope.produceNumbers() = produce<Int> {
for (i in 1..100) {
send(i)
delay(10)
}
}
suspend fun channelExample() = coroutineScope {
val channel = produceNumbers()
repeat(5) {
println("Received: ")
}
channel.cancel()
}
Android + Coroutines
# android_coroutines.kt — Coroutines in Android
import kotlinx.coroutines.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
// === ViewModel + Coroutines ===
class UserViewModel : ViewModel() {
private val userRepository = UserRepository()
// viewModelScope — auto-cancel เมื่อ ViewModel ถูก destroy
fun loadUser(userId: String) {
viewModelScope.launch {
try {
_loading.value = true
// IO dispatcher สำหรับ network/database
val user = withContext(Dispatchers.IO) {
userRepository.getUser(userId)
}
// กลับมา Main thread อัตโนมัติ
_user.value = user
} catch (e: Exception) {
_error.value = e.message
} finally {
_loading.value = false
}
}
}
// Parallel loading
fun loadDashboard() {
viewModelScope.launch {
try {
val profile = async(Dispatchers.IO) { userRepository.getProfile() }
val notifications = async(Dispatchers.IO) { notificationRepo.getAll() }
val feed = async(Dispatchers.IO) { feedRepo.getLatest() }
_dashboard.value = Dashboard(
profile = profile.await(),
notifications = notifications.await(),
feed = feed.await()
)
} catch (e: Exception) {
_error.value = "Failed to load dashboard"
}
}
}
}
// === Repository Pattern ===
class UserRepository(private val api: UserApi, private val db: UserDao) {
// Room + Retrofit + Coroutines
suspend fun getUser(id: String): User {
return try {
val user = api.getUser(id) // Retrofit suspend function
db.insertUser(user) // Room suspend function
user
} catch (e: Exception) {
db.getUser(id) ?: throw e // Fallback to cache
}
}
}
// === Retrofit Suspend Function ===
interface UserApi {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: String): User
@GET("users")
suspend fun getUsers(): List<User>
}
Coroutines vs Threads
# comparison.py — Coroutines vs Threads comparison
import json
class CoroutinesVsThreads:
COMPARISON = {
"memory": {
"coroutines": "เบามาก — สร้าง 100,000 ตัวใช้ RAM ไม่กี่ MB",
"threads": "หนัก — 1 thread ใช้ ~1MB stack, 100,000 threads = 100GB",
},
"creation": {
"coroutines": "สร้างเร็วมาก — microseconds",
"threads": "สร้างช้ากว่า — milliseconds",
},
"switching": {
"coroutines": "Context switch เบา — ไม่ต้อง OS involvement",
"threads": "Context switch หนัก — OS kernel involvement",
},
"cancellation": {
"coroutines": "Cooperative cancellation — structured concurrency",
"threads": "ยากที่จะ cancel safely — interrupt + cleanup",
},
"error_handling": {
"coroutines": "Structured — parent catch child exceptions",
"threads": "Manual — uncaught exception handler",
},
"debugging": {
"coroutines": "ง่ายกว่า — sequential-looking code",
"threads": "ยาก — race conditions, deadlocks",
},
}
def show_comparison(self):
print("=== Coroutines vs Threads ===\n")
for key, comp in self.COMPARISON.items():
print(f"[{key}]")
print(f" Coroutines: {comp['coroutines']}")
print(f" Threads: {comp['threads']}")
print()
def benchmark(self):
print("=== Benchmark: 100,000 tasks ===")
print(f" Coroutines: ~1 second, ~50 MB RAM")
print(f" Threads: Impossible (OutOfMemoryError) or very slow with thread pool")
print(f" Virtual Threads (Java 21): ~2 seconds, ~200 MB RAM")
print(f"\n Winner: Kotlin Coroutines (lightweight + structured concurrency)")
comp = CoroutinesVsThreads()
comp.show_comparison()
comp.benchmark()
FAQ - คำถามที่พบบ่อย
Q: Coroutines คืออะไร ต่างจาก Thread อย่างไร?
A: Coroutines เป็น lightweight concurrency primitive — เบากว่า thread มาก (สร้างได้เป็นแสนตัว) Thread = OS-level, หนัก, 1MB/thread Coroutine = language-level, เบา, หลาย coroutines แชร์ thread เดียว ข้อดี: code อ่านง่ายเหมือน sequential, structured concurrency, cancellation ง่าย
Q: suspend function คืออะไร?
A: suspend function = function ที่สามารถ "หยุดชั่วคราว" (suspend) แล้ว resume ทีหลังได้ โดยไม่ block thread ใช้ keyword "suspend" นำหน้า เรียกได้เฉพาะใน coroutine หรือ suspend function อื่น ตัวอย่าง: delay(), withContext(), network calls, database queries Compiler จะแปลง suspend function เป็น state machine อัตโนมัติ
Q: Dispatcher ไหนใช้เมื่อไหร่?
A: Dispatchers.Main: อัปเดต UI (Android) Dispatchers.IO: Network, File I/O, Database (thread pool ใหญ่) Dispatchers.Default: CPU-intensive (calculation, sorting, parsing) Dispatchers.Unconfined: Testing, ไม่ค่อยใช้ใน production กฎ: ใช้ withContext() เปลี่ยน dispatcher — อย่า launch ใน wrong dispatcher
Q: Flow คืออะไร ต่างจาก LiveData?
A: Flow = cold reactive stream — ส่งค่าหลายตัวตามลำดับ (เหมือน RxJava Observable) LiveData = lifecycle-aware observable — ใช้เฉพาะ Android UI Flow ดีกว่า: operators เยอะกว่า (map, filter, combine), ใช้ได้ทุก layer LiveData ง่ายกว่า: lifecycle-aware อัตโนมัติ, เหมาะ UI layer แนะนำ: ใช้ Flow ทั้ง project → แปลงเป็น LiveData ที่ ViewModel ด้วย asLiveData()
