一、协程构建器 (Coroutine Builders)

  • launch:创建一个新的协程,并返回一个Job,代表协程本身。它不返回任何结果,适用于“发后即忘”的场景。
  • async:创建一个新的协程,返回一个Deferred<T>对象(Job的子接口)。通过在其上调用.await(),可以挂起并获取最终返回的结果。常用于并发执行任务。
  • runBlocking:创建一个新的协程并阻塞当前线程,直到其内部所有逻辑执行完毕。它主要用于桥接普通阻塞代码和挂起风格代码(如在main函数或测试中),日常Android开发中应避免使用。

launchasync 必须通过 CoroutineScope 才能调用,是其扩展函数。

二、构造参数/协程上下文(CoroutineContext)

一个协程对应一个CoroutineContext

官方出处:https://developer.android.google.cn/kotlin/coroutines/coroutines-adv?hl=zh-cn

CoroutineContext 使用以下元素集定义协程的行为:

>>>上下文元素的分类

1.调度器(CoroutineDispatcher)

决定协程在哪个线程或线程池上执行。主要类型有:

  • Dispatchers.Main:主线程,用于UI操作。
  • Dispatchers.IO:适合执行磁盘或网络I/O操作(如文件读写、网络请求)。
  • Dispatchers.Default:适合执行CPU密集型任务(如列表排序、JSON解析)。
  • Dispatchers.Unconfined:不限定线程,在第一个挂起点之前运行在当前线程,之后由调用resume的线程决定(极少使用)。

2.名称(CoroutineName)

3.异常处理器(CoroutineExceptionHandler )

4....其它任何CoroutineContext的子类

>>>上下文元素的传入方式

1.方式一:直接传入单个元素

fun main() = runBlocking<Unit> {
    // 1. 传入调度器
    launch(Dispatchers.IO) {
        println("IO调度器: ${Thread.currentThread().name}")
    }

    // 2. 传入协程名称
    launch(CoroutineName("MyCoroutine")) {
        println("协程名称: ${coroutineContext[CoroutineName]?.name}")
    }

    // 3. 传入 Job
    val parentJob = Job()
    launch(parentJob) {
        println("使用自定义 Job")
    }

    // 4. 传入异常处理器
    val handler = CoroutineExceptionHandler { _, throwable ->
        println("异常: ${throwable.message}")
    }
    launch(handler) {
        throw RuntimeException("出错了")
    }

    delay(100)
}

2.方式二:使用 + 组合多个元素(最常用)

fun main() = runBlocking<Unit> {
    // 组合多个元素
    launch(Dispatchers.IO + CoroutineName("组合协程")) {
        println("调度器: ${coroutineContext[CoroutineDispatcher]}")
        println("名称: ${coroutineContext[CoroutineName]?.name}")
    }

    // 组合更多元素
    val handler = CoroutineExceptionHandler { _, _ -> }
    launch(Dispatchers.Default + CoroutineName("完整") + handler + Job()) {
        println("所有元素都存在")
    }
}

3.方式三:自定义CoroutineScope

自定义CoroutineScope可以传入上下文参数,launch如果不传入上下文参数则会继承CoroutineScope的上下文。

与launch上下文的关系
launch 就像是一个"上下文继承器",它把 Scope 的上下文作为"默认值",把传入的上下文作为"覆盖值",合并后作为新协程的运行环境。

... (还有很多其它传入方式)

>>>上下文元素的获取

// 统一的获取语法
coroutineContext[元素类型]      // 推荐
coroutineContext[元素类型.Key]  // 显式使用 Key

启动模式 (CoroutineStart)

>>>上下文切换(重点!!!)

withContext 的核心作用就是切换协程的上下文,特别是切换线程

重点:withContext不会创建新协程,会“阻塞”当前协程。

特性 withContext launch
是否创建新协程 ❌ 否 ✅ 是
是否挂起 ✅ 是 ✅ 是
返回结果 ✅ 返回 T ❌ 返回 Job
异常传播 直接抛出 传播给父协程
父子关系 当前协程的子任务 新协程
  • 切换线程

    fun main() = runBlocking {
    println("① 主线程: ${Thread.currentThread().name}")
    
    withContext(Dispatchers.IO) {
        println("② 切换到IO线程: ${Thread.currentThread().name}")
        delay(100)
    }
    
    println("③ 自动切回原线程: ${Thread.currentThread().name}")
    }
  • 返回值

特性 withContext async
返回值类型 直接返回 T 返回 Deferred
是否需要 await 不需要 需要 await()
执行时机 立即执行,挂起等待 立即执行,可稍后 await
适用场景 需要结果并等待 可以稍后获取结果

三、启动模式 (CoroutineStart)

  • DEFAULT立即调度执行协程,随时可以取消。
  • LAZY懒加载模式,只有在需要时(如调用Job.start()Job.join()Deferred.await())才会开始执行。
  • ATOMIC:类似于DEFAULT,但在开始运行前不能被取消
  • UNDISPATCHED:协程创建后,立即在当前线程执行,直到遇到第一个挂起点。

0 条评论

发表回复

您的电子邮箱地址不会被公开。