Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?

0 阅读2分钟

Coroutine协程介绍

协程是一种轻量级线程,它通过 挂起suspend恢复resume的机制,在单线程内以同步的代码写法实现异步、非阻塞操作,从而更高效地管理并发任务,简化回调地狱和复杂的多线程切换,一句话总结协程:协程可以实现用同步的代码写出异步并发逻辑,既高效又易维护

协程特点:

  • 轻量高效:相比线程更轻量,一个线程可同时运行成千上万个协程。
  • 结构化并发:通过作用域(CoroutineScope)管理任务,方便取消、超时控制和生命周期管理
  • 同步写法,异步执行:代码看起来像同步逻辑,但实际是异步非阻塞执行。
  • 可挂起、可恢复:支持 suspend 函数,遇到耗时任务时可以挂起,不阻塞线程。
  • 线程切换方便:通过调度器(Dispatchers)轻松切换主线程、IO线程等。

协程 关于更多协程可以参见:深入理解Kotlin协程

当在协程中设置线程调度器时,通过 CoroutineScope(Dispatchers.xxx) 设置的是默认的调度器;而通过 launch(Dispatchers.Main)设置的是当前协程的调度器,且会覆盖默认设置。

调度器示例

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}")
}

执行结果:

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

两者区别

  1. 调度器设置层级不同
CoroutineScope(Dispatchers.xxx)launch(Dispatchers.xxx)
作用范围默认值显式指定/立即生效
影响范围整个作用域的所有协程仅当前启动的协程
优先级低(可被覆盖)高(覆盖默认值)
架构意义设置整体策略设置具体任务策略
  1. 代码执行流程对比
flowchart TD
    A[Case 1: scope.launch] --> B[继承Scope的默认IO调度器]
    B --> C["在IO线程执行<br>输出: DefaultDispatcher-worker-1"]
    
    D[Case 2: scope.launchDispatchers.Main] --> E[显式指定Main调度器]
    E --> F["在主线程开始执行<br>输出: main"]
    F --> G[withContextDispatchers.Default]
    G --> H["切换到Default线程池<br>输出: DefaultDispatcher-worker-3"]
    H --> I[切换回Main调度器]
    I --> J["回到主线程继续执行<br>输出: main"]

如何选择

1、使用 CoroutineScope(Dispatchers.xxx) + 默认launch

// 数据层作用域 - 所有操作默认在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、混合使用

// 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
            }
        }
    }
}