定義
協(xié)程(Coroutine)是Kotlin提供的一種輕量級線程,用于簡化異步編程。它可以在單線程內(nèi)實現(xiàn)并發(fā)操作,通過掛起(suspend) 和恢復(fù)(resume)機制,讓異步代碼看來像同步代碼一樣直觀。
協(xié)程的核心是掛起函數(shù)(suspend),它可以在不阻塞當(dāng)前線程的情況下等待操作完成,完成后自動恢復(fù)執(zhí)行。
特點
-
輕量級
協(xié)程運行在用戶態(tài),不直接依賴操作系統(tǒng)線程,可以在一個線程中運行成千上萬個協(xié)程,開銷極小。
-
結(jié)構(gòu)化并發(fā)
協(xié)程通過作用域(CoroutineScope)管理生命周期,確保所有字協(xié)程在父協(xié)程完成前完成,避免資源泄露。
-
取消機制
協(xié)程支持協(xié)作取消,可以隨時取消不再需要的任務(wù),釋放資源。
-
異步處理簡單
使用常規(guī)的
try-catch即可捕捉協(xié)程內(nèi)部異常,或者通過CoroutineExceptionHandler統(tǒng)一處理。 -
與Android生命周期集成
官方提供了
lifecycleScope(Activity/Fragment)和viewModelScope(ViewModel),自動感知生命周期,在銷毀時自動取消協(xié)程。 -
調(diào)度器(Dispatcher)
協(xié)程可以靈活切換線程:
Dispathchers.Main(主線程)、Dispatchers.IO(IO密集型任務(wù))、Disapatchers.Default(CPU密集型任務(wù))等。
Kotlin 協(xié)程作用域
Kotlin 協(xié)程主要有以下幾種作用域構(gòu)建器:
GlobalScope(全局作用域)
- 生命周期:應(yīng)用程序整個生命周期
- 使用場景:不推薦使用,僅適用于與應(yīng)用程序生命周期的相同任務(wù)
GlobalScope.launch {
// 不推薦
}
特點:
- 不需要在任何范圍內(nèi),即可啟動對象
- 容易導(dǎo)致內(nèi)存泄漏
- 無法自動取消
- 以及被標(biāo)記
@DelicateCoroutinesApi??
lifecycleScope(生命周期作用域)
- 生命周期:與
Lifecycle綁定(Activity/Fragment) - 使用場景:UI相關(guān)的協(xié)程操作
lifecycleScope.launch {
// 推薦用于 Activity/Fragment
}
特點:
- 自動管理生命周期
- Lidecycle銷毀時自動取消
- 需要引入
lifecycle-runtime-ktx庫 - 推薦使用?
viewModeScope(ViewModel 作用域)
- 生命周期:與
ViewModel綁定 - 使用場景:在ViewModel中執(zhí)行后臺任務(wù)
viewModelScope.launch {
// 推薦用于 ViewModel
}
特點:
- ViewModel清除時候自動取消
- 需要引入
lifecycle-viewmodel-ktx - 推薦使用?
coroutineScope(協(xié)程作用域)
- 生命周期:等待所有的子協(xié)程完成
- 使用場景:需要等待所有的子任務(wù)完成的場景
coroutineScope {
launch { /* 任務(wù) 1 */ }
launch { /* 任務(wù) 2 */ }
// 等待所有任務(wù)完成
}
特點:
- 會阻塞當(dāng)前協(xié)程直到所有的子協(xié)程完成場景
- 失敗會傳播異常,一個失敗就會拋出異常一損俱損
- 結(jié)構(gòu)化并發(fā)
SupervisorScop(監(jiān)督作用域)
- 生命周期:等待所有的子協(xié)程完成
- 使用場景:子任務(wù)相互獨立,一個失敗不影響其他任務(wù)
supervisorScope {
launch {
// 失敗不會影響其他協(xié)程
}
launch {
// 繼續(xù)執(zhí)行
}
}
特點:
- 子協(xié)程失敗不會導(dǎo)致其他協(xié)程取消,獨立失敗,互不影響
- 需要手動處理異常
- 更靈活的錯誤處理
CoroutineScope(自定義作用域)
- 生命周期:手動控制
- 使用場景:自定義生命周期管理
class MyRepository {
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
fun doWork() {
scope.launch {
// 后臺工作
}
}
fun cleanup() {
scope.cancel()
}
}
特點:
- 需要手動創(chuàng)建和取消
- 靈活性更高
- 需要謹(jǐn)慎管理避免內(nèi)存泄漏
協(xié)程作用域?qū)φ毡?/h4>
| 作用域 | 生命周期 | 自動取消 | 異常傳播 | 使用場景 | 推薦度 |
|---|---|---|---|---|---|
| GlobalScope | 應(yīng)用全程 | ? | ? | 不推薦使用 | ?? 不推薦 |
| lifecycleScope | Lifecycle | ? | ? | UI 層 (Activity/Fragment) | ????? |
| viewModelScope | ViewModel | ? | ? | ViewModel 層 | ????? |
| coroutineScope | 子協(xié)程完成 | ? | ? | 需要等待所有子任務(wù) | ???? |
| supervisorScope | 子協(xié)程完成 | ? | ? | 子任務(wù)相互獨立,互不影響 | ???? |
| CoroutineScope | 手動控制 | ? | 可配置 | 自定義場景 | ??? |
Kotlin 協(xié)程啟動器:launch、async、runBlocking 詳解
Launch
//源代碼
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
特點
- 返回類型:Job 【不返回結(jié)果】
- 使用場景:“發(fā)射并忘記”,不需要返回結(jié)果的異步操作
- 異常處理:異常會被傳播到
CoroutineExceptionHandler或者 父協(xié)程
參考案例:
// 在已有的協(xié)程作用域中
val job = coroutineScope.launch {
delay(1000)
println("執(zhí)行后臺任務(wù)")
}
// 可以取消任務(wù),有內(nèi)鬼終止交易
job.cancel()
async
//源代碼
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyDeferredCoroutine(newContext, block) else
DeferredCoroutine<T>(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
特點
- 返回類型:
Deferred<T>可等待的泛型結(jié)果 - 使用場景:需要返回結(jié)果的異步操作,可并行執(zhí)行多個任務(wù)
- 異常處理:異常會在調(diào)用
wawit()時候拋出
參考案例:
val scope = CoroutineScope(Dispatchers.IO + Job())
// 并行執(zhí)行多個任務(wù)
val deferred1 = scope.async { fetchData1() }
val deferred2 = scope.async { fetchData2() }
// 等待結(jié)果
val result1 = deferred1.await()
val result2 = deferred2.await()
// 或使用 awaitAll 并行等待
val results = listOf(deferred1, deferred2).awaitAll()
runBlocking
//源代碼
public actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
val currentThread = Thread.currentThread()
val contextInterceptor = context[ContinuationInterceptor]
val eventLoop: EventLoop?
val newContext: CoroutineContext
if (contextInterceptor == null) {
// create or use private event loop if no dispatcher is specified
eventLoop = ThreadLocalEventLoop.eventLoop
newContext = GlobalScope.newCoroutineContext(context + eventLoop)
} else {
// See if context's interceptor is an event loop that we shall use (to support TestContext)
// or take an existing thread-local event loop if present to avoid blocking it (but don't create one)
eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
?: ThreadLocalEventLoop.currentOrNull()
newContext = GlobalScope.newCoroutineContext(context)
}
val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
return coroutine.joinBlocking()
}
特點
- 返回類型:
T阻塞當(dāng)前線程直到完成 - 使用場景:橋接同步和異步代碼,測試環(huán)境
- 異常處理:直接拋出異常
參考案例
// 阻塞主線程
fun main() {
runBlocking {
val result = async { loadData() }.await()
println(result)
}
// 上面的代碼會阻塞直到協(xié)程完成
}
// 測試中使用
@Test
fun testCoroutine() = runBlocking {
val result = repository.getData()
assertEquals("expected", result)
}
對比總結(jié)表 launch、async、runBlocking
| 啟動器 | launch | async | runBlocking |
|---|---|---|---|
| 返回值 | Job | Deferred<T> | T |
| 是否阻塞 | 否 | 否 | 是 |
| 獲取結(jié)果 | ? | ? await() | ? 直接返回 |
| 使用場景 | 不關(guān)心結(jié)果 | 需要結(jié)果 | 測試/橋接 |
| UI 線程可用 | ? | ? | ? |
| 性能開銷 | ??? | ?? | ? |
suspend 掛起狀態(tài)
一個掛起任務(wù)runSuspendTalks
suspend fun runSuspendTalks(): String {
log("協(xié)程掛起任務(wù) runSuspendTalks ")
delay(1000 * 3)
return "100"
}
反編譯成Java語音查看區(qū)別
// 方法簽名添加了 Continuation 參數(shù)
@Nullable
public final String runSuspendTalks(@NotNull Continuation<? super runSuspendTalks> continuation) {
// 檢查 Continuation 是否是特定狀態(tài)機的實例
if (continuation instanceof runSuspendTalks$CoroutineImpl) {
runSuspendTalks$CoroutineImpl coroutineImpl = (runSuspendTalks$CoroutineImpl)continuation;
int label = coroutineImpl.label;
coroutineImpl.label = 0;
// 根據(jù)狀態(tài)執(zhí)行不同的代碼段
if (label == 0) {
// 正常執(zhí)行邏輯
this.log("協(xié)程掛起任務(wù) runSuspendTalks ");
// 調(diào)用 delay 時會掛起
Object result = DelayKt.delay(3000L, coroutineImpl);
if (result == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
return result; // 掛起,返回掛起點
}
// 恢復(fù)執(zhí)行
return "100";
} else if (label == 1) {
// 從 delay 掛起點恢復(fù)
// ... 繼續(xù)執(zhí)行后續(xù)代碼
return "100";
}
}
// 創(chuàng)建新的狀態(tài)機實例
runSuspendTalks$CoroutineImpl impl = new runSuspendTalks$CoroutineImpl(this, continuation);
return impl.invokeSuspend(Unit.INSTANCE);
}
============================== 生成的狀態(tài)機類(偽代碼): ===========================================
// 編譯器生成的狀態(tài)機類
final class runSuspendTalks$CoroutineImpl extends CoroutineImpl {
int label;
Object L$0; // 局部變量存儲
public final Object invokeSuspend(Object result) {
// 狀態(tài)機核心邏輯
switch(label) {
case 0:
// 第一次執(zhí)行
label = 1;
this.result = result;
break;
case 1:
// 從掛起恢復(fù)
break;
}
return result;
}
}
關(guān)鍵點
- Continuation參數(shù):每個
suspend函數(shù)都會多出一個Continuation<T>參數(shù),用于回調(diào)恢復(fù) - 狀態(tài)機:編譯器將
suspend函數(shù)轉(zhuǎn)換成有限狀態(tài)機,通過label標(biāo)記執(zhí)行位置 - 掛起點:每次調(diào)用其他
suspend函數(shù)(如:delay)時:- 保存當(dāng)前狀態(tài)到
Continuation - 返回特殊值 COROUTINE_SUSPENDED
- 異步操作完成后,通過Continuation.resume()恢復(fù)
- 保存當(dāng)前狀態(tài)到
- 局部變量提示:suspend函數(shù)中的局部變量會提升狀態(tài)機的字段
withContent調(diào)度器
可用withContent切換協(xié)程上下文的線程,如 I/O 操作、CPU 密集型任務(wù)等
suspend fun fetchUserData(): String {
// 在 IO 線程執(zhí)行網(wǎng)絡(luò)請求或數(shù)據(jù)庫操作
val userData = withContext(Dispatchers.IO) {
// 執(zhí)行耗時的 I/O 操作
performNetworkRequest()
}
// 自動返回到原始上下文(可能是主線程)
// 更新 UI
updateUI(userData)
return userData
}
-
Dispatchers.Main:Android 主線程,用于更新 UI -
Dispatchers.IO:適用于 I/O 密集型任務(wù),如網(wǎng)絡(luò)請求、文件讀寫 -
Dispatchers.Default:適用于 CPU 密集型任務(wù),例如算法,非I/O操作流 -
Dispatchers.Unconfined:無限制調(diào)度器,很少使用
cancel 取消協(xié)程
- Job.cancel()
val job = launch {
// 協(xié)程任務(wù)
}
job.cancel() // 取消協(xié)程
- async 返回的是 Deferred<T>(繼承自 Job),取消方式類似 deferred.cancel()
val deferred = scope.async {}
// 取消 async 協(xié)程
deferred.cancel()
- 取消整個 CoroutineScope,及父作用域
val scope = CoroutineScope(Dispatchers.IO + Job())
scope.launch { /* ... */ }
scope.coroutineContext[Job]?.cancel() // 取消作用域內(nèi)所有協(xié)程