背景
偶然遇到一个需求,把串行的大任务拆分成更小颗粒度的小任务依次串行执行。听起来没啥用,但是在现实场景中,是个很有效改善TTR的工作。因为主线程中的任务是不能被打断的,而所有用户操作都需要插入到队列中处理,一旦一个计算任务又大又长还在主线程,用户一定很难受。由此react提出了fiber,官宣也挺有用的。
跟协程啥关系
其实最舒服的接口就是协程类的接口,通过套嵌或顺序发起任务进行大任务拆分,以语义化dsl的方式减少阅读和理解成本,以闭包在任务间传递参数。而且协程也可以直接拿来做这个需求,毕竟自定义一个Dispatcher就能搞定一切。
几十行的轮子
然而不造轮子很没意思,不如自己写几十行实现一个只拆分任务的伪协程。
当然协程的实现还真就不是能靠现有语言特性能直接模拟出来的,因为协程其实靠的是语言在编译过程中对代码进行了二进制进行了修改(约等于transform)。但是,思路上基本是一致的,所以简单复刻就只有下面这几十行,而且可以做到不依赖复杂transform。
fun main() {
with(SplitRun()) {
run {
println("aaa")
val a = 0
run {
println("bbb ${a}")
run {
println("ccc")
}
run {
println("ddd")
}
}
run {
println("eee")
}
}
run {
println("e")
}
commit()
}
}
typealias Run = () -> Unit
typealias StackFrame = LinkedList<Run>
class SplitRun(private val handler: Scheduler = Schedulers.newThread()) {
private var currentRun: Run = {}
private val rootFrame = StackFrame()
init {
currentRun.setFrame(rootFrame)
}
fun run(runnable: Run) {
val frame = currentRun.getFrame()
frame.add(runnable)
}
fun commit() {
handler.run {
val run = rootFrame.poll()
currentRun = run
run.setFrame(StackFrame())
run()
val frame = run.getFrame()
frame.asReversed().forEach { rootFrame.addFirst(it) }
run.removeFrame()
if (rootFrame.isNotEmpty()) {
commit()
}
}
}
}
val runFrame = mutableMapOf<Run, StackFrame>()
fun Run.setFrame(frame: StackFrame) {
runFrame[this] = frame
}
fun Run.getFrame(): StackFrame {
return runFrame[this]!!
}
fun Run.removeFrame() {
runFrame.remove(this)
}