coroutine语义对kotlin的异步执行进行了简化,要了解内部细节,首先有一定的线程调度基本功,利于理解,比如线程中是是sleep方法与yield方法对线程的中断差异,join方法的含义的等可以先了解一下。另外线程池概念顺势巩固一下,有利于了解下文中的调度器行为。
线程切换,需要切换上下文(压栈出栈操作),由操作系统管理,代价大。kotlin中coroutine在android平台上,主要是通过JVM来管理切换,可以不涉及线程切换,当前的线程被继续执行其他的coroutine里的task。
一 基本调用方式
有以下几种调用方式:
CoroutineScope.launch,
CoroutineScope.async,
withContext,
runBlocking(自动阻塞到当前线程,直到job执行结束),
其中CoroutineScope的接口定义如下:
public interface CoroutineScope { public val coroutineContext: CoroutineContext}
public fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit): Job
通过launch接口,主要有context参数还有一个执行体block,返回一个Job对象。其中CoroutineScope的实例由ContextScope,GlobalScope类(实现Coroutinescope接口)来构造。
其中CoroutineContext的定义如下:
@SinceKotlin("1.3")
public interface CoroutineContext {
public operator fun <E : Element> get(key: Key<E>): E?
public fun <R> fold(initial: R, operation: (R, Element) -> R): R
public operator fun plus(context: CoroutineContext): CoroutineContext = ...
public fun minusKey(key: Key<*>): CoroutineContext
public interface Key<E : Element>
public interface Element : CoroutineContext {
public val key: Key<*>
...
}
}
参考文2中指出,这个接口类似List接口,有添加,删除,增加List等类似操作方式。这样针对结构化的并行任务,可以将对应的上下文的CoroutineContext(此处为Element,CoroutineContext的子类型, 包括Job, Deferred, CoroutineDispacher, ActorCoroutine等)进行适当地删减或者添加,来对子scope进行定义。类似如图:
图1 scope的结构示意
block内可以暂停或者忽略执行(Canceled时)通过修饰符suspend表达。通过kotlinc编译后,大概有如下的变换:
// kotlin
suspend fun updateUserInfo(name: String, id: Long): User
// JVM
public final Object updateUserInfo(String name, long id, Continuation<User> $completion)
其中Continuation接口如下:
public interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(result: Result<T>)
}
continuation可以被理解成通用的callback。二 实现的基本原理
从第一部分了解到,Continuation一旦被挂起,将自己当前的执行对象暂存起来,当回调时,调用它的resume接口,并将暂存对象传入,完成Coroutine后续的suspend的工作。
其中状态机类似如下,每个suspend函数在编译期间被设定为一个label,如下图中的L0,L1,L2。
图2 状态机示意
三 调度机制
根据文档介绍,目前有四类调度器,分别为:
Default,Atomic,Lazy,UnDispatched。
由于Kotlin大方向上是跨平台的,所以目前基于其他平台,Node.js,Javascript这类单线程的平台,当方式任务调度时,其实没有发生线程切换。
最常见的是Default,Lazy两类,其他两类从文档定义处于试验阶段。Default是默认进入调度等待模式,一旦调度器就绪,就立马执行。Lazy方式返回的Job,有两种启动方式,Job.start是显示的调度执行,Job.join是隐式的调度执行。
调度器继承于CoroutineDispatcher,由此可以推出也是实现了CoroutineContext接口。
我们可以自己实现自定义的调度器,比如可以利用固定线程池,或者单一线程等来实现Job调度。具体的事例可以参见文章1。
四 与其他机制的区别
vs promise/future机制
Promise机制内部也是回调函数的包装,但没有调度器的定义;
vs async/await
C#语言等,而kotlin仅仅加了一个suspend修饰符,其他都是库来支持的。async默认是异步的,需要await来执行获得异步结果。而coroutine默认是同步执行的(也有封装的async方法);
vs Rxjava
同样有调度器,相对语法方面较为复杂,以后文章中可以比较。
参考:
1. www.jianshu.com/p/090e35508…