kotlin协程-基础设施篇-协程创建与启动:SafeContinuation

种一颗树的最好时机是十年前,其次是现在。
学习也一样。
跟着霍老师的《深入理解 Kotlin 携程》学习一下协程。

在这里,我们将 kotlin 中的协程实现分为两个层次

  • 基础设施层:标准的协程 API,主要对协程提供了概念和语义上最基本的支持。
  • 业务框架层:协程的上层框架支持

协程的构造

我们可以很快捷的创建一个简单的协程

1
2
3
4
5
6
7
8
9
10
val continuation = suspend {
println("In Coroutine")
5
}.createCoroutine(object : Continuation<Int>{

override fun resumeWith(result: Result<Int>) {
println("Coroutine end :$result")
}
override val context: CoroutineContext = EmptyCoroutineContext
})

函数解析

接下来我们仔细的探查一下这个createCoroutine函数。
首先是它的声明:

1
2
3
public fun <T> (suspend () -> T).createCoroutine(
completion: Continuation<T>
): Continuation<Unit> =

这里的(suspend () -> T)是函数createCoroutine的 Receiver(接收者),简单解释一下:

带接收者的函数类型

是一种特殊的函数类型,它允许您在函数类型中指定一个接收者对象,使得在函数体内可以直接访问该接收者对象的成员函数和属性。这种函数类型的语法是在函数类型声明之前添加接收者类型。
带接收者的函数类型的语法如下:

接收者类型.() -> 返回类型

举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
data class Person(val name: String)

// 带接收者的函数类型
val greeting: Person.() -> String = {
"Hello, $name!"
}

// 扩展函数
fun Person.greet(): String {
return "Hello, $name!"
}
fun main() {
val person: Person = Person("huang")

// 使用带接收者的函数类型调用函数
val message1 = person.greeting()

// 使用扩展函数调用函数
val message2 = person.greet()

println(message1) // 输出: Hello, huang!
println(message2) // 输出: Hello, huang!
}

详细了解可以看kotlin 专栏中的这一篇,
Kotlin 中的函数类型及 Lambda 表达式:SAM 转换,带接收者的函数类型,匿名函数.

createCoroutine 说明

在了解了上面的知识之后,我们再回过头来看下createCoroutine函数

  • Receiver 是一个被 suspend 修饰的挂起函数,这也是协程要执行的代码,就先叫它协程体
  • 它需要一个Continuation<T>类型的参数completion,这个参数会在协程执行完后调用,实际上就是协程的完成回调
  • 返回值是一个 Continuation<Unit>对象,由于createCoroutine只是创建了协程,后面我们还需要使用这个返回对象启动协程

协程启动

启动协程非常简单,只需要调用continuation.resume(Unit)就可以了。

continuation 解释

下面我们简单看一下continuation这个对象是啥。打上断点,我们可以看到它是一个SafeContinuation实例。
continuation
我们再回过头看下createCoroutine这个函数,实际上返回的就是SafeContinuation实例
SafeContinuation
这里传入了两个参数,一个是delegate和一个COROUTINE_SUSPENDED
当我们执行 resume 的时候,实际上调用的是delegateresumeWith方法。
resume
我们进入SafeContinuation类中,发现该类的resumeWith实际上调用的就是上面传入的delegateresumeWith方法,并且这里的 delegate 类型是HelloKt$main$continuation$1这个类型特别像是匿名内部类的名字,仔细观察会发现这个匿名类的名字是<FileName>Kt$<FunctionName>$continuation$1这种形式,猜测应该是编译器在编译的过程中添加了一些现在我们还暂时用不到的东西,让我们传入的 suspend Lambda 表达式生成了匿名内部类

resumeWith
当执行delegate.resume方法时,我们的suspend Lambda 表达式得到执行,并且在执行完毕后,调用了传入的completion参数的 resumeWith 方法。

其他方式

其实还有一个更方便的创建并启动协程的方法

1
2
3
4
5
6
7
8
9
10
suspend {
println("In Coroutine")
5
}.startCoroutine(object : Continuation<Int>{

override fun resumeWith(result: Result<Int>) {
println("Coroutine end :$result")
}
override val context: CoroutineContext = EmptyCoroutineContext
})

startCoroutine
实际上只是帮我们调用了一下 resume, 哈哈哈,当然了,返回值也不一样了。

协程体的 Receiver

除了上面提到的两个方法外,我们还注意到另外两个方法
createCoroutine receiver
startCoroutine receiver
这两个方法和上面的两个方法,只是多了一个协程体的 Receiver 类型 R。这个 R 可以为协程体提供一个作用域,在协程体内可以直接使用作用域内提供的函数或者字段。

怎么用

假设我们有一个数据类

1
data class Student(val name: String, val age: Int)

创建协程并启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 val coroutineBlock : suspend Student.() -> Int = {
println("In Coroutine ${this.name}")
this.age
}

val continuation = coroutineBlock.createCoroutine(Student("John", 23), completion = object : Continuation<Int> {
override val context: CoroutineContext
get() = EmptyCoroutineContext
override fun resumeWith(result: Result<Int>) {
println("Coroutine end :$result")
}

})
continuation.resume(Unit)

这样,我们就可以在suspend Lambda表达式中使用 Student 类提供函数和字段了。


以上就是协程的创建和启动以及相关的一些零碎知识点,接下来我们一起学习一下函数的挂起


kotlin协程-基础设施篇-协程创建与启动:SafeContinuation
https://blog.huangyuanlove.com/2025/11/24/kotlin协程-基础设施篇-协程创建与启动:SafeContinuation/
作者
HuangYuan_xuan
发布于
2025年11月24日
许可协议
BY HUANG兄