Kotlin之coroutine机制探究

597 阅读4分钟

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… 

2. m.imooc.com/article/det…

3. medium.com/swlh/everyt…