# Kotlin 协程启动构建器:CoroutineStart

`launch` 函数的第二个参数是 `CoroutineStart`,用于设置协程的启动模式。在了解各启动模式前,需要先区分两个容易混淆的概念:**立即调度**和**立即执行**。

launch 函数签名

`launch` 的函数签名大致如下:

```kotlin

fun CoroutineScope.launch(

context: CoroutineContext = EmptyCoroutineContext,

start: CoroutineStart = CoroutineStart.DEFAULT,

block: suspend CoroutineScope.() -> Unit

): Job

```

其中,`start` 参数决定协程以什么方式启动。

立即调度与立即执行

立即调度

立即调度是指:

> 调用 `launch` 后,协程任务立刻被提交给对应的调度器,等待线程执行。

任务被调度不代表协程体会在调用点立即运行。

```kotlin

fun main() = runBlocking {

println("A")

launch {

println("B")

}

println("C")

}

```

通常输出:

```text

A

C

B

```

执行到 `launch` 时,子协程已经进入待调度状态,但当前线程仍在执行 `runBlocking` 中的后续代码,因此先打印 `C`。调度器随后执行子协程并打印 `B`。

> 输出顺序与具体调度器和运行环境有关。这个例子用于说明"已提交调度"不等于"协程体已经执行"。

立即执行

立即执行是指:

> 调用协程构建器时,直接在当前调用线程中开始执行协程体,直到遇到第一个真正挂起点。

```kotlin

fun main() = runBlocking {

println("A")

launch(start = CoroutineStart.UNDISPATCHED) {

println("B")

delay(100)

println("D")

}

println("C")

}

```

通常输出:

```text

A

B

C

D

```

`UNDISPATCHED` 会立即在当前线程执行 `println("B")`。执行到 `delay(100)` 并真正挂起后,`launch` 调用才返回,外层代码继续打印 `C`。子协程恢复后再打印 `D`。

CoroutineStart 的四种启动模式

1. DEFAULT:立即调度

```kotlin

launch(start = CoroutineStart.DEFAULT) {

println("执行任务")

}

```

特点:

  • 调用 `launch` 后立即将任务提交给调度器。

  • 不保证协程体立刻开始执行。

  • 协程在真正开始执行前可以被取消。

  • 这是默认模式,也是最常用的模式。

示例:

```kotlin

fun main() = runBlocking {

val job = launch(start = CoroutineStart.DEFAULT) {

println("任务开始")

}

job.cancel()

}

```

如果取消操作发生在协程体开始执行之前,`任务开始` 可能不会输出。

2. LAZY:延迟启动

```kotlin

val job = launch(start = CoroutineStart.LAZY) {

println("任务开始")

}

```

创建协程时不会立即启动。以下操作可以触发启动:

```kotlin

job.start()

job.join()

```

示例:

```kotlin

fun main() = runBlocking {

println("A")

val job = launch(start = CoroutineStart.LAZY) {

println("B")

}

println("C")

job.join()

println("D")

}

```

输出:

```text

A

C

B

D

```

注意事项:如果创建了一个 `LAZY` 子协程,却既不启动也不取消它,父协程结束时仍需等待该子协程,可能导致程序一直等待。

3. ATOMIC:原子启动

```kotlin

launch(start = CoroutineStart.ATOMIC) {

println("任务开始")

delay(100)

println("任务结束")

}

```

特点:

  • 创建后立即调度。

  • 从创建到开始执行之前,取消操作不能阻止协程进入协程体。

  • 协程开始执行并到达首个可取消挂起点后,恢复正常的协作式取消行为。

  • 适用于必须保证初始化逻辑至少开始执行的特殊场景。

示例:

```kotlin

@OptIn(ExperimentalCoroutinesApi::class)

fun main() = runBlocking {

val job = launch(start = CoroutineStart.ATOMIC) {

println("一定有机会执行")

delay(100)

println("可能不会执行")

}

job.cancelAndJoin()

}

```

通常会输出:

```text

一定有机会执行

```

调用 `cancelAndJoin()` 时,取消操作无法阻止协程进入协程体,因此第一行有机会执行。当代码到达 `delay()` 等可取消挂起点时,会检测到取消状态,后续代码通常不再执行。

> `ATOMIC` 属于特殊启动模式,并涉及实验性 API,业务代码中应谨慎使用。

4. UNDISPATCHED:立即执行

```kotlin

launch(start = CoroutineStart.UNDISPATCHED) {

println("立即执行")

delay(100)

println("恢复执行")

}

```

特点:

  • 不先经过调度器,直接在当前调用线程执行协程体。

  • 一直执行到第一个真正挂起点。

  • 挂起后再次恢复时,按照协程上下文中的调度器运行。

  • 如果调用的挂起函数没有真正挂起,协程会继续同步执行。

示例:

```kotlin

fun main() = runBlocking {

println("外层线程:${Thread.currentThread().name}")

launch(

context = Dispatchers.Default,

start = CoroutineStart.UNDISPATCHED

) {

println("挂起前:${Thread.currentThread().name}")

delay(100)

println("恢复后:${Thread.currentThread().name}")

}

}

```

可能输出:

```text

外层线程:main

挂起前:main

恢复后:DefaultDispatcher-worker-1

```

这说明 `UNDISPATCHED` 模式下,协程体在首次挂起前运行于调用线程;挂起后恢复时,才使用协程上下文中的 `Dispatchers.Default`。

启动模式对比

| 启动模式 | 创建后是否启动 | 开始方式 | 启动前的取消行为 | 典型用途 |

| --- | --- | --- | --- | --- |

| `DEFAULT` | 是 | 立即提交调度 | 可以在执行前取消 | 默认业务场景 |

| `LAZY` | 否 | 调用 `start()`、`join()` 等操作后调度 | 启动前可以取消 | 按需启动 |

| `ATOMIC` | 是 | 原子调度启动 | 取消不能阻止协程进入协程体 | 必须保证开始执行的特殊任务 |

| `UNDISPATCHED` | 是 | 在当前线程立即执行到首次挂起 | 初始代码会直接执行 | 同步初始化、减少首次调度 |

核心理解

> `DEFAULT` 是立即把任务提交给调度器,但不保证协程体马上运行;`UNDISPATCHED` 才是在调用点直接执行协程体,直到第一次真正挂起。

可以简单记忆为:

  • `DEFAULT`:现在调度。

  • `LAZY`:以后启动。

  • `ATOMIC`:保证启动。

  • `UNDISPATCHED`:现在执行。

相关推荐
XS0301063 小时前
Spring AI:两种内置 Advisor 快速实现 RAG
java·人工智能·spring
不爱说话郭德纲3 小时前
我只给了 TRAE Work 一张差评截图,它最后却把自己的 P0 结论推翻了?
前端·后端·架构
mifengxing3 小时前
LeetCode 238 除自身以外数组的乘积|无除法O(n)时间+O(1)空间双解法
java·算法·leetcode
用户938515635074 小时前
从 DOM 编程到声明式 UI:React useRef 与 useState 底层全解析
前端·javascript·react.js
kisshyshy4 小时前
前端路由进化史:从刷新白屏到SPA,手写一个Hash路由就懂了!
前端·javascript·react.js
小林敲代码77884 小时前
Spring Boot 3 升级踩坑:aj-captcha 行为验证码底图加载失效
java·spring boot·后端
Sterting4 小时前
第 8 节:表单 — 前端校验的第一道防线
前端·javascript
tedcloud1234 小时前
Kimi-K3 部署指南:大模型应用开发环境搭建实践
linux·运维·服务器·开源·音视频
保加利亚的风4 小时前
Docker 学习文档(Mac + Docker Desktop 版)
前端·后端