扩展函数(以及
lambda
)可以被标记为
suspend
。这样方便了用户创建其他
DSLs
以及扩展其它
API
。有些情况下,库的作者需要阻止用户添加新的挂起线程的
方案。
这时就需要
@RestrictsSuspension
注解了。当一个接收者类或者接口
R
被标注
时,所有可挂起扩展都需要代理
R
的成员或者其它扩展。由于扩展时不能互相无
限代理(会导致程序终止),这就保障了所有挂起都是通过调用
R
的成员,这样
库作者就能完全掌控挂起方式了。
不过这样的场景不常见,它需要所有的挂起都通过库的特殊方式实现。比如,用下
面的
buildSequence()
函数实现生成器时,必须保证协程中所有的挂起都是通
过调用
yield()
或者
yieldAll()
来实现。这就是为什么
SequenceBuilder
被标注为
@RestrictsSuspension
:
@RestrictsSuspension
public
abstract
class
SequenceBuilder
<in T> {
...
}
可以参看
Github
源码
协程内部机制
这里并不打算全盘解释协程内部的工作原理,而是给大家一个整体上的概念。
125
协程
协程完全时通过编译技术(并不需要
VM
或者
OS
方面的支持)实现,挂起时借由
代码转换实现。基本上所有的挂起函数(当然是有些优化措施,但这里我们不会深
入说明)都被转换为状态机。在挂起前,下一个状态会存储在编译器生成的与本地
变量关联的类中。到恢复协程时,本地变量会被恢复为挂起之前的状态。
挂起的协程可以存储以及作为一个对象进行传递,该协程会继续持有其状态和本地
变量。这样的对象的类型时
Continuation
,代码转换的整体实现思路是基于经
典的
Continuation-passing style
。所有挂起函数要有一个额外的参数类
型
Continuation
。
更多的细节可以参看
设计文档
。其它语言(比如
C# ECMASript2016
)中类似的
async/await
模型在这里都有描述,当然了其它语言的实现机制和
Kotlin
有所不同
协程的实验状态
协程的设计是
实验性
的,也就是说在后面的
releasees
版本中可能会有所变更。当
在
Kotlin1.1
中编译协程时,默认会有警告:
The feature "coroutines" is
experimental
。可以通过
opt-in flag
来移除警告。
由于处于实验状态,协程相关的标准库都
在
kotlin.coroutines.experimental
包下。当设计确定时实验状态将会取消,
最后的
API
将会移到
kotlin.coroutines
,
实验性的包将会保留(或许是作为一个
单独的构建中)以保持兼容。
千万注意
:
建议库作者可以采用同样的转换:为基于协程的
API
采用
"experimental"
前缀作包名(比如
com.example.experimental
)。当最终
API
发布时,遵循下面的步骤:
复制所有
API
到
com.example
包下
保留实验性大包做兼容。
这样可以减少用户的迁移问题。
底层
API
:
kotlin.coroutines
底层
API
比较少,强烈建议不要使用,除非要创建高级库。这部分
API
主要在两个
包中:
kotlin.coroutines.experimental
带有主要类型与下述原语
createCoroutine()
startCoroutine()
suspendCoroutine()
kotlin.coroutines.experimental.intrinsics
带有更底层的内联函数如
suspendCoroutineOrReturn
关于这些
API
用法的更多细节可以在
这里
找到。
kotlin.coroutines
中的生成器
API
:
kotlin.coroutines.experimental
中唯一的
“
应用层面
”
的函数是:
buildSequence()
buildIterator()
这些和
kotlin-stdlib
打包在一起,因为和序列相关。事实上,这些函数(这
里单独以
buildSequence()
作为事例)实现生成器提供了一种更加简单的构造
延迟序列的方法:
val
fibonacciSeq
= buildSequence {
var
a
=
0
var
b
=
1
yield(
1
)
while
(
true
) {
yield(a + b)
val
tmp
= a + b
a = b
b = tmp
}
}
这里通过调用
yield()
函数生成新的斐波那契数,就可以生成一个无限的斐波那
契数列。当遍历这样的数列时,每遍历一步就生成一个斐波那契数,这样就可以从
中取出无限的斐波那契数。比如
fibonacciSeq.take(8).toList()
会返回
[1,
1, 2, 3, 5, 8, 13, 21]
。协程让这一实现开销更低。
为了演示正真的延迟序列,在
buildSequence()
中打印一些调试信息:
val
lazySeq
= buildSequence {
print(
"START "
)
for
(i
in
1..5
) {
yield(i)
print(
"STEP "
)
}
print(
"END"
)
}
// Print the first three elements of the sequence
lazySeq.take(
3
).forEach { print(
"$it "
) }
运行上面的代码运,如果我们输出前三个元素的数字与生成循环的
STEP
有交
叉。这意味着计算确实是惰性的。要输出
1
,我们只执行到第一个
yield(i)
,并且过程中会输出
START
。然后,输出
2
,我们需要继续下一个
128
协程
yield(i)
,并会输出
STEP
。
3
也一样。永远不会输出再下一个
STEP
(以
及
END
),因为我们没有请求序列的后续元素。
使用
yieldAll()
函数可以一次性生成序列所有值:
val
lazySeq
= buildSequence {
yield(
0
)
yieldAll(
1..10
)
}
lazySeq.forEach { print(
"$it "
) }
buildIterator()
与
buildSequence()
作用相似,只不过返回值时延迟迭代
器。
通过给
SequenceBuilder
类写挂起扩展,可以给
buildSequence()
添加自定
义生成逻辑:
suspend
fun
SequenceBuilder<Int>.
yieldIfOdd
(x: Int)
{
if
(x %
2
!=
0
) yield(x)
}
val
lazySeq
= buildSequence {
for
(i
in
1..10
) yieldIfOdd(i)
}