协程-来龙去脉

首先必须声明,此文章是观看油管《KotlinConf 2017 - Introduction to Coroutines by Roman Elizarov》的感想,如何可以,更建议您去观看这个视频而不是阅读本篇文章

代码的异步控制

举个例子,假设你要去论坛上发布消息,你必须先获取token以表明你的身份,然后创建一个消息,最后去发送它

kotlin 复制代码
//这是一个耗时操作
fun requestToken():Token = {
    ... //block to wait to receive token
    returen token
}

//这也是一个耗时操作
fun creatMessage() : Message ={
    ... //block to wait to creat Message
    returen token
}

fun sendMessage(token :Token,message:Message)

fun main() {
   	val token =  requestToken()
	val meassage =  creatMessage()
    sendMessage(token,message)
}

这种情况显然不符合我们的现实情况,我们不可能在没有拿到token就等待在那里,我们可以开启一个线程去异步执行

kotlin 复制代码
fun requestToken():Token = {
   ... //new thread to request token
    returen token
}

像这样一个任务我们就需要创建一个线程,creatMessage与requestToken可以并行进行,因此我们需要再次创建一个线程去执行creatMessage,从而创建两个线程。现在我们的手机性能很很高,我们可以创建一个,两个,甚至是一百个个线程去执行任务,但是到达一千个,一万个呢,恐怕手机的不足以支撑。

怎么解决这样的问题呢。我们只需要建立一种通知机制,在token返回后告诉我们,我们再继续完成creatMessage,进而sendMessage。这也就是callback方式

kotlin 复制代码
fun requestTokenCallback(callback : (Token) -> Unit) {
    ... //block to wait to receive token
    callback.invoke(token)
}

fun creatMessageCallback : (Message) -> Unit){
    ... //nblock to wait to creat Message
    callback.invoke(message)
}

fun sendMessage(token :Token,message:Message)

fun main() {
    //创建一个线程
    Thead {
        requestTokenCallback { token ->
            creatMessageCallback { message ->
                {
                    sendMessage(token, message)
                }
            }
        }

    }

}

这仅仅是一个简单的案例,就产生了如此多的嵌套和连续的右括号,在实际业务中往往更为复杂,比如请求失败或者一些异常情况,甚至是一些特定的业务操作,想想这样叠加下去,简直是灾难。

如何解决这种问题呢,java中有一个CompleteFuture,正如其名,它能够异步处理任务,以期获取未来的结果去处理,我们只需要允诺我们将来在某个时间点一定会返回某种类型的数据,我们就可以以预知未来的方式使用他,

kotlin 复制代码
//创建一个线程去异步执行
fun requestToken() :CompletableFuture<Token> = ...

//创建一个线程去异步执行
fun creatMessage(token) : CompletableFuture<Send> = ...

fun sendMessage(send :Send)

fun main() {
    requestToken()
        .thenCompose{token -> creatMessage(token)}
        .thenAccept{send -> sendMessage(send)}
}

令人头疼的代码已不复存在,我们可以自由组合我们任务

creatMessage的调用方式和sendMessage并不相同,kotlin为了统一这两种调用,产生了一个suspend 关键字,它能够像拥有魔法一样,让世界的时间暂停,自己又不会暂停,然后去执行自己的任务,执行完成之后,时间恢复,任务继续执行

kotlin 复制代码
suspend fun requestToken() :Token = ...

suspend fun creatMessage() : message = ...


fun sendMessage(token :Token,message:Message)

fun main() {
   	val token =  requestToken()
	val meassage =  creatMessage()
    sendMessage(token,message)
}

执行这段代码的时候,会发生编译错误,因为main函数只是一个普通的函数,并没有被suspend标记,当requestToken使时间暂停的同时,主程序也时间暂停了,那这与最开始的阻塞方法有什么不一样的呢

协程Coroutine

本文要介绍的是协程,协程是什么呢,在我看来,协程就是一个容器,让suspend标记的函数可以运行,可以开启魔法 ,让它在时间暂停的同时,并不影响主线程

kotlin 复制代码
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,//创建容器的上下文
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T//拥有魔法的函数
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)  //创建协程
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block) //启动魔法开关
    return coroutine
}

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext, //创建容器的上下文
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit //拥有魔法的函数
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy) //创建协程
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block) //启动魔法开关,启动协程
    return coroutine
}

我们可以通过Deferred.await() 获取将来的值T。自此,我们有了新的代码

kotlin 复制代码
suspend fun requestToken() :Token = ...

suspend fun creatMessage() : message = ...


fun sendMessage(token :Token,message:Message)

fun main() {
   	val token =  async {requestToken()}
	val meassage =  async{creatMessage()}
    sendMessage(token.await() ,message.await() )
}

实际业务中,我们的任务并不是一直都是需要返回结果的,所以还有另一种容器,只需要去执行

kotlin 复制代码
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

轻量级线程

协程被视为轻量级线程,轻量在哪呢?

实际上,async与launch 并没有什么魔法,只是将封装好的任务交由线程池去执行,所以suspend标记的函数可以任意暂停

协程只是代码层级的概念,操作系统对于此是无感知的,但是线程作为cpu调度的基本单位,创建和调用是很重的,需要中断机制去进行调度,消耗很多额外的资源,所以协程被视为轻量级线程。

上面的代码中launch 与async 都是CoroutineScope 的函数,那么CoroutineScope 是什么呢

这个就是协程运行的温床,也可说是运行的基础,也就是作用域。创建处一个协程,必须有一个管理容器去管理协程的创建,分发,调度,这就是CoroutineScope。

有一种常见的需求是网络执行完成切换UI线程去执行,因此,我们需要在创建容器的时候需要一个参数,去声明协程到底在线程池中执行,UI线程池还是普通线程池,

scss 复制代码
val scope = CoroutineScope(Dispatchers.IO) //IO线程池去处理
val scope = CoroutineScope(Dispatchers.Main) // UI线程去处理

关于我

一个希望友友们能提出建议的代码下毒糕手

相关推荐
alexhilton9 小时前
Jetpack Compose的性能优化建议
android·kotlin·android jetpack
天枢破军12 小时前
【KMP】桌面端打包指南
kotlin
_一条咸鱼_12 小时前
深度解析 Android MVI 架构原理
android·面试·kotlin
好学人13 小时前
Android MVVM 架构中的重要概念
kotlin·mvvm
好学人13 小时前
一文弄懂 repeatOnLifecycle
kotlin·mvvm
天枢破军13 小时前
【KMP】解决桌面端打包异常无法运行
kotlin
zhangphil13 小时前
Android ExifInterface rotationDegrees图旋转角度,Kotlin
android·kotlin
好学人14 小时前
一文弄懂Kotlin中的by关键字
kotlin
好学人19 小时前
Kotlin中的作用域关键字
kotlin
QING6181 天前
详解:Kotlin 类的继承与方法重载
android·kotlin·app