Kotlin协程作用域
外观
Kotlin协程作用域[编辑 | 编辑源代码]
介绍[编辑 | 编辑源代码]
Kotlin协程作用域(CoroutineScope)是协程结构化并发(Structured Concurrency)的核心机制,它定义了协程的生命周期范围,并确保协程之间的父子关系能够正确管理。作用域的主要职责包括:
- 跟踪所有在其内部启动的协程
- 提供取消所有子协程的能力
- 确保当作用域被取消时,所有子协程都会被自动清理
在结构化并发模型中,每个协程都必须在一个作用域内运行,这防止了协程泄漏(coroutine leaks)并简化了资源管理。
基本概念[编辑 | 编辑源代码]
协程作用域的类型[编辑 | 编辑源代码]
Kotlin提供了几种预定义的作用域:
作用域类型 | 描述 | 适用场景 |
---|---|---|
GlobalScope | 全局作用域,生命周期与应用程序一致 | 顶级长时间运行的任务(谨慎使用) |
CoroutineScope | 通用作用域,需手动管理生命周期 | 大多数业务场景 |
MainScope | 主线程作用域(UI线程) | Android/桌面应用UI操作 |
viewModelScope | Android ViewModel专用作用域 | Android MVVM架构 |
lifecycleScope | 与生命周期组件绑定的作用域 | Android生命周期感知操作 |
创建自定义作用域[编辑 | 编辑源代码]
通过CoroutineScope()
工厂函数创建作用域时需要指定协程上下文:
// 创建带有IO调度器的自定义作用域
val customScope = CoroutineScope(Dispatchers.IO + Job())
// 使用作用域启动协程
customScope.launch {
// 协程体
}
// 取消作用域及其所有子协程
customScope.cancel()
父子关系与结构化并发[编辑 | 编辑源代码]
协程层次结构[编辑 | 编辑源代码]
当在一个协程中启动另一个协程时,会自动建立父子关系:
这种关系带来两个关键特性: 1. 父协程取消时,所有子协程会自动取消 2. 父协程会等待所有子协程完成后再完成自己
代码示例[编辑 | 编辑源代码]
fun main() = runBlocking {
val parentJob = launch {
// 子协程1
launch {
repeat(5) { i ->
println("Child 1: $i")
delay(100)
}
}
// 子协程2
launch {
repeat(5) { i ->
println("Child 2: $i")
delay(150)
}
}
}
delay(250)
println("Cancelling parent")
parentJob.cancel() // 会取消所有子协程
parentJob.join()
println("Parent completed")
}
输出:
Child 1: 0 Child 2: 0 Child 1: 1 Child 2: 1 Child 1: 2 Cancelling parent Parent completed
作用域构建器[编辑 | 编辑源代码]
Kotlin提供了几种作用域构建器来创建临时作用域:
coroutineScope[编辑 | 编辑源代码]
创建一个独立的作用域,会等待所有子协程完成后才完成:
suspend fun fetchUserData() = coroutineScope {
val userProfile = async { fetchProfile() }
val userFriends = async { fetchFriends() }
UserData(
profile = userProfile.await(),
friends = userFriends.await()
)
}
supervisorScope[编辑 | 编辑源代码]
创建一个监督作用域,子协程的失败不会影响其他子协程:
suspend fun processTasks() = supervisorScope {
val task1 = launch { processTask1() } // 如果失败
val task2 = launch { processTask2() } // 仍会继续执行
task1.join()
task2.join()
}
实际应用案例[编辑 | 编辑源代码]
Android Activity中的使用[编辑 | 编辑源代码]
在Android中正确处理协程生命周期:
class MainActivity : AppCompatActivity() {
private val scope = CoroutineScope(Dispatchers.Main + Job())
override fun onDestroy() {
super.onDestroy()
scope.cancel() // 避免内存泄漏
}
fun loadData() {
scope.launch {
val data = withContext(Dispatchers.IO) {
repository.fetchData()
}
updateUI(data)
}
}
}
网络请求组合[编辑 | 编辑源代码]
并行执行多个网络请求:
suspend fun fetchDashboardData(): DashboardData = coroutineScope {
val userDeferred = async { api.getUser() }
val newsDeferred = async { api.getNews() }
val adsDeferred = async { api.getAds() }
DashboardData(
user = userDeferred.await(),
news = newsDeferred.await(),
ads = adsDeferred.await()
)
}
数学表示[编辑 | 编辑源代码]
协程作用域可以形式化表示为:
其中取消操作遵循:
最佳实践[编辑 | 编辑源代码]
- 避免使用GlobalScope,除非是应用程序级别的长时间运行任务
- 对于UI组件,使用与生命周期绑定的作用域(如Android的lifecycleScope)
- 对于ViewModel,使用viewModelScope
- 在挂起函数中需要启动协程时,优先使用coroutineScope或supervisorScope
- 总是确保作用域在不再需要时被取消
常见问题[编辑 | 编辑源代码]
Q: 为什么我的协程在Activity销毁后还在运行? A: 可能是因为使用了GlobalScope或忘记取消自定义作用域。应该使用与生命周期绑定的作用域。
Q: coroutineScope和supervisorScope有什么区别? A: coroutineScope在子协程失败时会取消所有子协程,而supervisorScope允许其他子协程继续运行。
Q: 如何测试带有作用域的代码? A: 可以使用TestCoroutineScope(kotlinx-coroutines-test库)来控制虚拟时间进行测试。