跳至正文

🎯 協程基礎

📖 協程入門

要使用協程,首先必須了解協程建構器。讓我們來了解最基本的三個建構器!

💡 runBlocking

基本用法

import kotlinx.coroutines.*

fun main() = runBlocking { // 啟動協程!
println("Hello")
delay(1000) // 等待 1 秒
println("World!")
}

特點

fun main() {
println("Before")

runBlocking {
delay(1000)
println("Inside coroutine")
}

println("After") // 等待協程結束
}
// Before
// (等待 1 秒)
// Inside coroutine
// After

注意:runBlocking 會阻塞當前執行緒!主要只在測試main 函式中使用。

🚀 launch

啟動非同步任務

fun main() = runBlocking {
launch {
delay(1000)
println("Task 1")
}

launch {
delay(500)
println("Task 2")
}

println("Main")
}
// Main
// (500ms 後) Task 2
// (1000ms 後) Task 1

返回 Job

fun main() = runBlocking {
val job = launch {
repeat(5) { i ->
println("Working... $i")
delay(500)
}
}

delay(1300)
println("取消任務!")
job.cancel() // 取消任務
job.join() // 等待取消完成
}

管理多個任務

fun main() = runBlocking {
val jobs = List(5) { i ->
launch {
delay(1000L * i)
println("任務 $i 完成")
}
}

jobs.forEach { it.join() } // 等待所有任務
println("所有任務結束!")
}

⚡ async

接收結果

fun main() = runBlocking {
val deferred = async {
delay(1000)
return@async "結果值"
}

println("進行中...")
val result = deferred.await() // 等待結果
println("收到的值: $result")
}

launch vs async

fun main() = runBlocking {
// launch - 無結果
launch {
delay(1000)
println("launch 完成")
}

// async - 返回結果
val result = async {
delay(1000)
"async 完成"
}

println(result.await())
}

🎯 實戰範例

平行處理

import kotlin.system.measureTimeMillis

fun main() = runBlocking {
val time = measureTimeMillis {
val one = async { fetchData1() }
val two = async { fetchData2() }

println("結果: ${one.await()}, ${two.await()}")
}

println("所需時間: ${time}ms") // ~1000ms (平行執行)
}

suspend fun fetchData1(): String {
delay(1000)
return "資料1"
}

suspend fun fetchData2(): String {
delay(1000)
return "資料2"
}

循序 vs 平行

fun main() = runBlocking {
// ❌ 循序執行 (慢)
val time1 = measureTimeMillis {
val one = fetchData1()
val two = fetchData2()
println("$one, $two")
}
println("循序: ${time1}ms") // ~2000ms

// ✅ 平行執行 (快)
val time2 = measureTimeMillis {
val one = async { fetchData1() }
val two = async { fetchData2() }
println("${one.await()}, ${two.await()}")
}
println("平行: ${time2}ms") // ~1000ms
}

例外處理

fun main() = runBlocking {
val deferred = async {
delay(1000)
throw Exception("發生錯誤!")
}

try {
deferred.await()
} catch (e: Exception) {
println("捕獲的錯誤: ${e.message}")
}
}

🔥 實用模式

逾時

fun main() = runBlocking {
try {
withTimeout(1300) {
repeat(3) { i ->
println("任務 $i")
delay(500)
}
}
} catch (e: TimeoutCancellationException) {
println("逾時!")
}
}

多個 API 呼叫

data class UserData(
val profile: String,
val posts: List<String>,
val friends: List<String>
)

suspend fun loadUserData(userId: String): UserData = coroutineScope {
val profile = async { fetchProfile(userId) }
val posts = async { fetchPosts(userId) }
val friends = async { fetchFriends(userId) }

UserData(
profile.await(),
posts.await(),
friends.await()
)
}

suspend fun fetchProfile(id: String): String {
delay(500)
return "Profile($id)"
}

suspend fun fetchPosts(id: String): List<String> {
delay(800)
return listOf("Post1", "Post2")
}

suspend fun fetchFriends(id: String): List<String> {
delay(600)
return listOf("Friend1", "Friend2")
}

重試邏輯

suspend fun <T> retryIO(
times: Int = 3,
delay: Long = 1000,
block: suspend () -> T
): T {
repeat(times - 1) { attempt ->
try {
return block()
} catch (e: Exception) {
println("嘗試 ${attempt + 1} 失敗")
delay(delay)
}
}
return block() // 最後嘗試
}

fun main() = runBlocking {
val result = retryIO(times = 3) {
fetchDataFromAPI()
}
println(result)
}

var attempt = 0
suspend fun fetchDataFromAPI(): String {
attempt++
if (attempt < 3) {
throw Exception("網路錯誤")
}
return "成功!"
}

🛠️ coroutineScope

結構化並行

suspend fun doWork() = coroutineScope {
launch {
delay(1000)
println("任務 1")
}

launch {
delay(2000)
println("任務 2")
}

println("所有任務啟動")
// 等待所有子任務結束
}

fun main() = runBlocking {
doWork()
println("完成!")
}

例外傳播

suspend fun riskyWork() = coroutineScope {
launch {
delay(500)
throw Exception("錯誤!")
}

launch {
delay(1000)
println("這段程式碼不會執行")
}
}

fun main() = runBlocking {
try {
riskyWork()
} catch (e: Exception) {
println("捕獲錯誤: ${e.message}")
}
}

🤔 常見問題

Q1. launch 和 async 的差異是?

A: 是否返回結果!

fun main() = runBlocking {
// launch - 不需要結果
launch {
println("簡單任務")
}

// async - 需要結果
val result = async {
"要返回的值"
}
println(result.await())
}

Q2. join() 和 await() 的差異是?

A: join 是等待完成,await 是等待結果!

fun main() = runBlocking {
val job = launch {
delay(1000)
}
job.join() // 只等待完成

val deferred = async {
delay(1000)
"結果"
}
val result = deferred.await() // 等待結果
}

Q3. 協程會自動取消嗎?

A: 父協程取消時,子協程也會取消!

fun main() = runBlocking {
val parent = launch {
val child = launch {
repeat(10) {
println("子任務 $it")
delay(500)
}
}

delay(1300)
println("父任務完成")
}

delay(2000)
parent.cancel() // 子任務也會一起取消
}

🎬 結語

您已經掌握了協程的基本建構器!

核心總結:
✅ runBlocking - 用於測試/main 函式
✅ launch - 不需要結果的任務
✅ async - 接收返回結果
✅ coroutineScope - 結構化並行
✅ 透過平行處理提升效能

下一步:在 suspend 函式中深入了解暫停函式!