Coroutine协程介绍
协程是一种轻量级线程,它通过 挂起suspend 和 恢复resume 的机制,在单线程内以同步的代码写法实现异步、非阻塞操作,从而更高效地管理并发任务,简化回调地狱和复杂的多线程切换,一句话总结协程:协程可以实现用同步的代码写出异步并发逻辑,既高效又易维护。
协程特点:
- 轻量高效:相比线程更轻量,一个线程可同时运行成千上万个协程。
- 结构化并发:通过作用域(CoroutineScope)管理任务,方便取消、超时控制和生命周期管理
- 同步写法,异步执行:代码看起来像同步逻辑,但实际是异步非阻塞执行。
- 可挂起、可恢复:支持 suspend 函数,遇到耗时任务时可以挂起,不阻塞线程。
- 线程切换方便:通过调度器(Dispatchers)轻松切换主线程、IO线程等。
关于更多协程可以参见:深入理解Kotlin协程
当在协程中设置线程调度器时,通过 CoroutineScope(Dispatchers.xxx) 设置的是默认的 调度器;而通过 launch(Dispatchers.Main)设置的是当前协程 的调度器,且会覆盖默认设置。
调度器示例
scss
val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
//1、创建CoroutineScope时设置调度器
scope.launch {
log("thread0:${Thread.currentThread().name}")
}
//2、启动协程时设置调度器
scope.launch(Dispatchers.Main) {
log("thread1:${Thread.currentThread().name}")
//3、通过withContext设置调度器
withContext(Dispatchers.Default) {
log("thread2:${Thread.currentThread().name}")
}
log("thread3:${Thread.currentThread().name}")
}
执行结果:
makefile
00:45:25.814 E thread0:DefaultDispatcher-worker-1
00:45:25.821 E thread1:main
00:45:25.822 E thread2:DefaultDispatcher-worker-3
00:45:25.865 E thread3:main
两者区别
- 调度器设置层级不同
CoroutineScope(Dispatchers.xxx) |
launch(Dispatchers.xxx) |
|
|---|---|---|
| 作用范围 | 默认值 | 显式指定/立即生效 |
| 影响范围 | 整个作用域的所有协程 | 仅当前启动的协程 |
| 优先级 | 低(可被覆盖) | 高(覆盖默认值) |
| 架构意义 | 设置整体策略 | 设置具体任务策略 |
- 代码执行流程对比
flowchart TD
A[Case 1: scope.launch] --> B[继承Scope的默认IO调度器]
B --> C["在IO线程执行
输出: DefaultDispatcher-worker-1"] D[Case 2: scope.launchDispatchers.Main] --> E[显式指定Main调度器] E --> F["在主线程开始执行
输出: main"] F --> G[withContextDispatchers.Default] G --> H["切换到Default线程池
输出: DefaultDispatcher-worker-3"] H --> I[切换回Main调度器] I --> J["回到主线程继续执行
输出: main"]
输出: DefaultDispatcher-worker-1"] D[Case 2: scope.launchDispatchers.Main] --> E[显式指定Main调度器] E --> F["在主线程开始执行
输出: main"] F --> G[withContextDispatchers.Default] G --> H["切换到Default线程池
输出: DefaultDispatcher-worker-3"] H --> I[切换回Main调度器] I --> J["回到主线程继续执行
输出: main"]
如何选择
1、使用 CoroutineScope(Dispatchers.xxx) + 默认launch
kotlin
// 数据层作用域 - 所有操作默认在IO线程
val dataScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
// 网络请求仓库
class UserRepository {
fun fetchUserData() = dataScope.launch {
//默认在IO线程执行网络请求、数据库操作
val user = apiService.getUser() // 在IO线程
database.saveUser(user) // 在IO线程
}
}
当 整个作用域 的大多数任务都需要相同类型的调度器(如:数据层都用IO调度器),想要减少重复代码,避免每个launch都指定调度器时,适合统一在CoroutineScope中来声明调度器。
2、混合使用
kotlin
// ViewModel
class MyViewModel : ViewModel() {
//默认在IO线程的作用域,用于后台工作,这里为了演示Scope,实际项目中可以直接使用viewModelScope
private val ioScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
fun loadData() {
//使用IO作用域进行网络请求
ioScope.launch {
val data = fetchFromNetwork()
withContext(Dispatchers.Main) {
processUI() //切换到主线程处理UI
}
}
}
}