协程
suspendCancellableCoroutine 阻塞当前协程,并是可取消的协程
//挂起当前协程,如果不执行resume或者cancel会一直挂起
val r = suspendCancellableCoroutine<String> {
cancellableContinuation: CancellableContinuation<String> ->
cancellableContinuation.invokeOnCancellation {
// 取消后回调,正常结束不会回调
}
cancellableContinuation.resume("3")//恢复协程,返回结果 r="3"
cancellableContinuation.cancel(Throwable(""))//取消协程的运行
cancellableContinuation.resumeWithException(Throwable(""))//带着错误恢复协程
}
coroutineScope 创建新的协程作用域
只能在协程中运行
CoroutineScope 创建新的协程作用域 哪里都可以运行
var coroutineScope = CoroutineScope(Dispatchers.Default)
coroutineScope.launch {
}
Semaphore 限制协程的数量 (原理:信号量控制)
val maxConcurrency = 3
val semaphore = Semaphore(maxConcurrency)
val jobs = (1..10).map {
launch {
semaphore.withPermit {
println("开始执行协程 $it")
delay(1000)
println("协程 $it 完成")
}
}
}
jobs.joinAll()
在这个例子中,我们创建了一个允许最多3个协程同时运行的Semaphore。通过withPermit方法,我们确保在执行协程时遵循这个限制。
协程源码类
SchedulerCoroutineDispatcher 协程任务分发
CoroutineScheduler 创建线程的子类Worker 分发任务 类似于线程池
协程执行过程
suspend fun gg() {
suspendCancellableCoroutine<String> {
println("")
}
}
上面代码中,会由于执行了suspendCancellableCoroutine而没有调用resume函数导致协程被挂起
是因为没有调用resume,suspendCancellableCoroutine会使gg返回 COROUTINE_SUSPENDED,源码如下:
如果返回 COROUTINE_SUSPENDED,会导致没有调用到后续代码,我们可以在下面的反编译代码中看到,只会执行case 0:,就return了,后面的lable执行不到了,就相当于挂起,当我们再次调用resumeWith时,这时的lable为1,才会执行case 1及后面的代码。
理解这些后,然后看一下协程执行中,是如何执行到这一步的
下面是调用栈
也就是说,由于suspend的标志,会由协程的环境调用这个函数,而 invokeSuspend是kotlin自动实现的函数,反编译java后可以看到这个实现,如图:
可以看到launch里面的执行内容都放到,该invokeSuspend里面执行了
launch返回的job执行->执行launch的块级函数->执行invokeSuspend
同样,如果直接返回COROUTINE_SUSPENDED,也会导致协程挂起,如图,只打印了一个sdf
dispatcher任务分发: 协程的执行会把要执行的内容封装成Task对象,然后放到Worker的localQueen的task队列中,如果task都执行完成后,会执行Unsafe.park,阻塞cpu,如果有新task进来,会调用一次Unsafe.unpark再将线程唤醒。
总结
所以协程的大致执行过程创建continuation(或者叫协程,因为Corountine实现了continuation)用来执行传进来的suspend函数 - > 通过interceptContinuation创建DispatchedContinuation,作为dispatcherh和continuation桥梁,DispatchedContinuation本身会引用continuation,同时也实现了Task类 -> 执行DispatchedContinuation的函数resumeWith,resumeWith会通过dispatcher将task(DispatchedContinuation)分发到线程池(或者叫work池)中的一个work(线程)的待执行队列中 -> 等待执行Task(也就是DispatchedContinuation),执行task时会调用continuation.resumeWith,用来执行传进来的suspend函数
注:Work是一个线程,继承了Thread
Flow
collectLatest 接收最新值,抛弃旧值
终端流操作符通过提供的action收集给定的流。与collect的关键区别在于,当原始流发出新值时,先前值的action块将被取消。
可以通过下面的例子来证明:
flow {
emit(1)
delay(50)
emit(2)
}.collectLatest { value ->
println("Collecting $value")
delay(100) // Emulate work
println("$value collected")
}
打印“Collecting 1,Collecting 2,Collecting 2”
原理就一行代码
public suspend fun <T> Flow<T>.collectLatest(action: suspend (value: T) -> Unit) {
mapLatest(action).buffer(0).collect()
}
mapLatest
collect
终端流运算符收集给定的流但忽略所有发出的值。如果在收集期间或在提供的流程中发生任何异常,则从此方法重新抛出此异常。
它是collect {}的简写。
该运算符通常与onEach 、 onCompletion和catch运算符一起使用,以处理所有发出的值并处理上游流中或处理过程中可能发生的异常,例如:
flow
.onEach { value -> process(value) }
.catch { e -> handleException(e) }
.collect() // trigger collection of the flow
- flow:这是创建Flow的最基本的函数。你可以在flow的lambda表达式中发射多个值。
val flow = flow {
emit(1)
emit(2)
}
- collect:这是用于收集Flow中的值的函数。它是一个挂起函数,所以它应该在协程中调用。
launch {
flow.collect { value ->
println(value)
}
}
- map:这个函数用于将Flow中的每个值转换成另一种形式。
val stringFlow = flow.map { value ->
value.toString()
}
- filter:这个函数用于过滤Flow中的值。
val evenFlow = flow.filter { value ->
value % 2 == 0
}
- reduce:这个函数用于将Flow中的所有值组合成一个单一的结果。
val sum = flow.reduce { accumulator, value ->
accumulator + value
}
- onEach:这个函数在每次从Flow中发射出一个值时调用。
flow.onEach { value ->
println("Value emitted: $value")
}
- catch:这个函数用于处理Flow中的异常。
flow.catch { e ->
println("Caught exception: $e")
}
- onStart:这个函数在开始收集Flow之前调用。
flow.onStart {
println("Flow started")
}
- onCompletion:这个函数在Flow收集完成后调用,无论成功还是失败。
flow.onCompletion { cause ->
if (cause != null) {
println("Flow completed with exception: $cause")
} else {
println("Flow completed successfully")
}
}
以上就是Kotlin Flow API中的一些主要函数,实际上还有更多的函数和操作符,如:buffer、conflate、zip等。
- buffer:用于在Flow中添加缓冲区,以便在发射和收集操作之间进行并发处理。
flow.buffer().collect { value ->
println(value)
}
带buffer的flow(buffer容量>0)是会异步执行,而不带buffer(或者buffer容量=0)的类型是同步执行,也就是说带有buffer的flow是可以收集和发送一起执行的,否则只能发送一个收集一个,依次执行。
- conflate:用于在发射速度快于收集速度时,丢弃中间值,只保留最新的值。
flow.conflate().collect { value ->
println(value)
}
- zip:用于将两个Flow的值按顺序组合成一个新的Flow。
val flow1 = flowOf(1, 2, 3)
val flow2 = flowOf("a", "b", "c")
val zippedFlow = flow1.zip(flow2) { value1, value2 ->
"$value1$value2"
}
- combine:用于将两个Flow的值组合成一个新的Flow,当任一Flow发射新值时都会触发组合。
val flow1 = flowOf(1, 2, 3)
val flow2 = flowOf("a", "b", "c")
val combinedFlow = flow1.combine(flow2) { value1, value2 ->
"$value1$value2"
}
- flatMapConcat:用于将Flow中的每个值转换为另一个Flow,并按顺序连接它们。
val nestedFlow = flowOf(flowOf(1, 2), flowOf(3, 4))
val flatFlow = nestedFlow.flatMapConcat { innerFlow ->
innerFlow
}
- flatMapMerge:用于将Flow中的每个值转换为另一个Flow,并同时处理它们。
val nestedFlow = flowOf(flowOf(1, 2), flowOf(3, 4))
val flatFlow = nestedFlow.flatMapMerge { innerFlow ->
innerFlow
}
- debounce:用于在Flow中添加去抖动操作,以便在指定的时间间隔内仅保留最后一个值。
flow.debounce(300).collect { value ->
println(value)
}
- distinctUntilChanged:用于过滤掉连续重复的值。
val flow = flowOf(1, 1, 2, 2, 3)
val distinctFlow = flow.distinctUntilChanged()
flow源码
调用flow函数是调用的safeFlow
public fun <T> flow(@BuilderInference block: suspend FlowCollector<T>.() -> Unit): Flow<T> = SafeFlow(block)
flow作用域是属于FlowCollector的
flow{
// FlowCollector作用域
}
SafeCollector实现了FlowCollector
emit是FlowCollector函数
emit执行过程
override suspend fun emit(value: T) {
return suspendCoroutineUninterceptedOrReturn sc@{ uCont ->
try {
emit(uCont, value)
} catch (e: Throwable) {
lastEmissionContext = DownstreamExceptionContext(e, uCont.context)
throw e
}
}
}
1.suspend和suspendCoroutineUninterceptedOrReturn标识异步执行,并在resumeWith之前阻塞当前协程
private fun emit(uCont: Continuation<Unit>, value: T): Any? {
......
//这里的emitFun会调用collector.emit(value) 这是kotlin函数对象的原始写法
//因为emitFun的定义写在了类外面,所以第一次参数是调用这个函数的对象,如果emitFun的定义写在了类里面,调用这个函数的对象就不需要作为该函数的参数。
val result = emitFun(collector as FlowCollector<Any?>, value, this as Continuation<Unit>)
.......
return result
}
所以现在相当于调用了collector.emit函数,collector是调用collect时,collect的唯一参数。
我们调用无参数的collect(),实际上是调用的扩展函数,里面有个空实现的NopCollector(Nop其实是一个汇编指令,表示什么也不做,但是cpu会去执行这个指令,但没有什么作用,也占用一个指令执行的时间)
所以这里是调用了空实现的emit
当我们调用collect()表示我要收集数据了,这里调用到了collectSafely,这是safeflow实现的一个方法,
所以这里相当于执行了flow的块级函数,高亮的那一部分就是
然后执行emit -> 执行NopCollect.emit方法
如果像执行map{}这样的函数呢?
然后看transfrom
unsafeFlow其实是一个简单的flow实现
他和SafeFlow的区别就是,safeFlow在执行collect时,是由SafeCollector来执行,因为SafeCollector本身是一个continuation,他在emit执行时会检查当前所在的协程环境是否合法,是否处于active状态,因为协程是可以被取消的。
比如:
我们可以看到一个基本的协程其实是实现了Job
现在回到unsafeFlow,其实随着一层层调用,最后还是回到了SafeFlow,这你就要问了,为什么要多创建一个unsafeFlow出来,因为我们想要执行emit函数 将处理过的值再发送到下游,下游再次调用colect时,我们不想让他再次执行到collect中的块级函数,所以多封装一层,只执行上一层的collect,而不执行上上一层的collect。
第一层的lambda函数会执行到emit,由emit调用第二层的lambda函数,逐级调用到最后。
flowOn可以设置一个上游协程环境,其实是将其collect函数,运行在设置的协程中,那么执行上几层的collect时都会运行在该协程环境中,又由于emit会继承协程环境,所以后面的collect块级函数同样也会运行在该协程环境中,其实不继承该协程环境也会运行在该协程中,只是取出协程对象,判断该协程对象是否处于active状态来判断是否发送出该值。
这些仅是Kotlin Flow API中的一部分函数和操作符。要了解更多,请参阅官方文档:Kotlin Flow API。
flow的buffer扩展函数,其中buffer的实现原理使用的是Channel,emit发送值,最终会调用到channel的send方法,接收到值后再通过emit方法发送到下游。
最后一张图总结:
Channel
场景:
协程:a 协程:b
在协程a发送消息,在协程b接收
相对于flow更灵活一点,flow对于发送消息灵活,但是接收消息不灵活。发送消息我们可以通过flowCollector的emit方法在任何地方发送(需要保存flowCollector对象),但是接收消息却在链式调用中已经写死了。而对于channel只需要send,receive即可。
Select
这样一个场景,两个 API 分别从网络和本地缓存获取数据,期望哪个先返回就先用哪个做展示,这是侯就需要用到select 对于支持select的地方有这些:
* | **Receiver** | **Suspending function** | **Select clause**
* | ---------------- | --------------------------------------------- | -----------------------------------------------------
* | [Job] | [join][Job.join] | [onJoin][Job.onJoin]
* | [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait]
* | [SendChannel] | [send][SendChannel.send] | [onSend][SendChannel.onSend]
* | [ReceiveChannel] | [receive][ReceiveChannel.receive] | [onReceive][ReceiveChannel.onReceive]
* | [ReceiveChannel] | [receiveCatching][ReceiveChannel.receiveCatching] | [onReceiveCatching][ReceiveChannel.onReceiveCatching]
* | none | [delay] | [onTimeout][SelectBuilder.onTimeout]
语法糖
class PP {
operator fun get(kk: PP) = 0
companion object Key {
}
}
get 相当于 []
例如:
val p = PP()
p[PP] 相当于 p.get(PP)
PP既可以当作伴随对象也可以当作PP类
伴随对象只能有一个
例如上面的p[PP]相当于p[PP.Key]
- 属性代理
在Kotlin中,属性代理是一种设计模式,允许你将属性的getter和setter操作委托给另一个对象。这个对象被称为代理对象,它需要实现getValue和setValue方法。
provideDelegate方法是Kotlin 1.1引入的一个新特性,它允许你在创建属性代理时进行一些额外的检查或初始化工作。
下面是一个使用provideDelegate和getValue方法的例子:
import kotlin.reflect.KProperty
class Delegate {
operator fun provideDelegate(thisRef: Any?, prop: KProperty<*>): Delegate {
println("provideDelegate for ${prop.name}")
return this
}
operator fun getValue(thisRef: Any?, prop: KProperty<*>): String {
return "$thisRef, thank you for delegating '${prop.name}' to me!"
}
}
class Example {
var p: String by Delegate()
}
fun main() {
val e = Example()
println(e.p)
}
在这个例子中,Delegate类实现了provideDelegate和getValue方法。当我们在Example类中创建一个由Delegate类代理的属性p时,provideDelegate方法会被调用,我们可以在这个方法中进行一些初始化工作。然后,当我们访问属性p时,getValue方法会被调用,我们可以在这个方法中定义属性的getter操作。
需要注意的是,provideDelegate方法是可选的,如果你的代理对象不需要在创建时进行一些额外的操作,你可以不实现这个方法。但是getValue方法是必须的,因为它定义了属性的getter操作。