阅读 1004

深入理解Kotlin协程(一)

协程的创建

来看个稍微复杂点的函数


fun main(){

    // 这里仅仅是创建协程
    val continuation= suspend {
        println("suspend thread:"+Thread.currentThread().id)
        // 协程体
        println("in coroutine.")
        5
    }.createCoroutine(object :Continuation<Int>{
        override val context: CoroutineContext
            get() = EmptyCoroutineContext

        override fun resumeWith(result: Result<Int>) {
            println("resumeWith thread:"+Thread.currentThread().id)
            println("coroutine end $result")
        }

    })

    // 这里才是 执行协程
    continuation.resume(Unit)

    //别让主线程直接结束了
    println("main thread:"+Thread.currentThread().id)
    Thread.sleep(10000)

}


复制代码

来看下执行结果:

image.png

创建协程的关键函数

看名字可以得知 createCoroutine 是创建协程的关键函数 我们来剖析一下它。

image.png

协程的启动

前面我们了解到 协程创建的过程,现在来看下协程是如何启动的

由前文可以看到 createCoroutine 函数的返回值是SafeContinudation,这个东西为啥 可以调用resume方法 来直接让协程启动呢?

我们debug 看一下:

image.png

我们重点看一下 TestKtmainmaincontinuation$1.invokeSuspend(Test.kt)

他实际上就是编译阶段生成的一个匿名内部类,这个匿名内部类有一个invokeSuspend方法,这个方法里面 其实就是我们的协程体,

讲白了 create函数 最终返回的 就是一个 大马甲,这个大马甲你看起来是什么Continuation ,其实本质上 就是我们 suspend lambda里面定义的那个 协程体

这就是为什么 我们调用resume 可以启动一个协程的原因。

我们当然可以通过start 函数 在创建一个协程以后 直接启动

    // 这里仅仅是创建协程
    val continuation= suspend {
        println("suspend thread:"+Thread.currentThread().id)
        // 协程体
        println("in coroutine.")
        5
    }.startCoroutine(object :Continuation<Int>{
        override val context: CoroutineContext
            get() = EmptyCoroutineContext

        override fun resumeWith(result: Result<Int>) {
            println("resumeWith thread:"+Thread.currentThread().id)
            println("coroutine end $result")
        }

    })

复制代码

函数的挂起

首先看下面的代码:

fun main(){
    GlobalScope.launch {
        println("GlobalScope thread start :"+Thread.currentThread().id+"   time:${System.currentTimeMillis()}")
        val result=suspendFunc02(1,2)
        println(result)
        println("GlobalScope thread end :"+Thread.currentThread().id+"   time:${System.currentTimeMillis()}")


    }
    println("main thread:"+Thread.currentThread().id)
    Thread.sleep(10000)
}



suspend fun suspendFunc02(a:Int,b:Int):Int{
    return suspendCoroutine<Int> {continuation ->
        println("suspendFunc02 start :"+Thread.currentThread().id+"   time:${System.currentTimeMillis()}")
        thread {
            println("suspendFunc02 thread in  :"+Thread.currentThread().id)
            Thread.sleep(5000)
            continuation.resumeWith(Result.success(a+b))
        }
        println("suspendFunc02 end :"+Thread.currentThread().id+"   time:${System.currentTimeMillis()}")
    }
}
复制代码

看下执行结果 能更加直观一些:

image.png

这里一定要注意 挂起函数并不一定真的会挂起,只是提供了挂起的条件。 比如我们用suspend 定义一个函数,如果这个函数里面没有 切换线程的操作,那么这个suspend 在ide中 会显示为灰色

image.png

问题来了,那到底什么时候才会被挂起?

出现异步调用才会真正的挂起,直到对应的Continutaion的resume函数被执行,调用才会恢复执行

这里要好好看下上面的 执行结果,多看几遍就能真正理解了

协程的上下文

前文我们在创建一个协程的过程中 看到了context 这个概念

image.png

来看下这个协程上下文到底是个啥呢 这个接口代码很少 我们直接上代码


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 =
        if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
            context.fold(this) { acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
                    // make sure interceptor is always last in the context (and thus is fast to get when present)
                    val interceptor = removed[ContinuationInterceptor]
                    if (interceptor == null) CombinedContext(removed, element) else {
                        val left = removed.minusKey(ContinuationInterceptor)
                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                            CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }

   
    public fun minusKey(key: Key<*>): CoroutineContext

   
    public interface Key<E : Element>

    
    public interface Element : CoroutineContext {
      
        public val key: Key<*>

        public override operator fun <E : Element> get(key: Key<E>): E? =
            @Suppress("UNCHECKED_CAST")
            if (this.key == key) this as E else null

        public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)

        public override fun minusKey(key: Key<*>): CoroutineContext =
            if (this.key == key) EmptyCoroutineContext else this
    }
}
复制代码

代码很简单,总结一下:

协程的上下文 本质上 就是一个集合,这个List的类型是Element, 要注意的是这个Element本身也是继承了CorotineContext 这个接口

到这里 我们应该就有一点敏感性了哈, 既然你的元素是一个element 接口,那我们看下有多少个class 继承了你这个接口 就可以知道 所谓的协程上下文 到底会有多少东西

这个技巧 我们在阅读很多源码的时候都会用到。

看下:

image.png

可以看出来 协程的上下文 中的element 子类 有很多,重点我们就关注一下 红色箭头标注的就可以了。

比如指定 协程的名称,拦截器, 等等。

我们可以尝试着创建自己的 协程Context 来加深对这个概念的理解(其实你可以把他想象为一个key value的键值对,虽然里面的存储数据结构是List 但你可以按照key-value的 形象来理解)

下面的例子我们创建了一个协程的context,指定了协程的名字 以及 异常处理器

fun main(){
      var  context=CoroutineName("testCoroutine")+ CoroutineExceptionHandler { coroutineContext, throwable ->
            println("handler exception!!!")
      }
    // 这里仅仅是创建协程
    suspend {
        println("in coroutine [${coroutineContext[CoroutineName]}]")
        throw IllegalArgumentException("do exception")
    }.startCoroutine(object :Continuation<Int>{
        override val context: CoroutineContext
            get() = context

        override fun resumeWith(result: Result<Int>) {
            result.onFailure {
                this.context[CoroutineExceptionHandler]?.handleException(this.context,it)
            }
        }
    })
    //别让主线程直接结束了
    println("main thread:"+Thread.currentThread().id)
    Thread.sleep(10000)
}

复制代码

可以看下执行结果:

image.png

文章分类
Android
文章标签