协程-来龙去脉

首先必须声明,此文章是观看油管《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线程去处理

关于我

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

相关推荐
小白学大数据22 分钟前
高级技术文章:使用 Kotlin 和 Unirest 构建高效的 Facebook 图像爬虫
爬虫·数据分析·kotlin
guitarjoy8 小时前
Kotlin - 协程结构化并发Structured Concurrency
kotlin·协程·coroutinescope·结构化同步
zhangphil20 小时前
Android使用PorterDuffXfermode模式PorterDuff.Mode.SRC_OUT橡皮擦实现“刮刮乐”效果,Kotlin(2)
android·kotlin
居居飒2 天前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
刘争Stanley3 天前
如何高效调试复杂布局?Layout Inspector 的 Toggle Deep Inspect 完全解析
android·kotlin·android 15·黑屏闪屏白屏
sickworm陈浩3 天前
Java 转 Kotlin 系列:究竟该不该用 lateinit?
android·kotlin
droidHZ4 天前
Compose Multiplatform 之旅—声明式UI
android·kotlin
zhangphil4 天前
Android基于Path的addRoundRect,Canvas剪切clipPath简洁的圆角矩形实现,Kotlin(1)
android·kotlin
alexhilton7 天前
Android技巧:学习使用GridLayout
android·kotlin·android jetpack
zhangphil7 天前
Android使用PorterDuffXfermode的模式PorterDuff.Mode.SRC_OUT实现橡皮擦,Kotlin(1)
android·kotlin