注册

协程-来龙去脉

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


代码的异步控制


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


//这是一个耗时操作
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就等待在那里,我们可以开启一个线程去异步执行



fun requestToken():Token = {
... //new thread to request token
returen token
}

像这样一个任务我们就需要创建一个线程,creatMessage与requestToken可以并行进行,因此我们需要再次创建一个线程去执行creatMessage,从而创建两个线程。现在我们的手机性能很很高,我们可以创建一个,两个,甚至是一百个个线程去执行任务,但是到达一千个,一万个呢,恐怕手机的不足以支撑。
怎么解决这样的问题呢。我们只需要建立一种通知机制,在token返回后告诉我们,我们再继续完成creatMessage,进而sendMessage。这也就是callback方式



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,正如其名,它能够异步处理任务,以期获取未来的结果去处理,我们只需要允诺我们将来在某个时间点一定会返回某种类型的数据,我们就可以以预知未来的方式使用他,


//创建一个线程去异步执行
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 关键字,它能够像拥有魔法一样,让世界的时间暂停,自己又不会暂停,然后去执行自己的任务,执行完成之后,时间恢复,任务继续执行



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标记的函数可以运行,可以开启魔法 ,让它在时间暂停的同时,并不影响主线程


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。自此,我们有了新的代码



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() )
}


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


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线程池还是普通线程池,


val scope = CoroutineScope(Dispatchers.IO) //IO线程池去处理
val scope = CoroutineScope(Dispatchers.Main) // UI线程去处理

题外


阅读到这里可以知道suspend实际上就是一个callback封装, 对于其如何将标记函数转化为可挂起恢复的,可浏览# Kotlin Vocabulary | 揭秘协程中的 suspend 修饰符,个人觉得讲的不错


在推荐的这篇文章中,可以看到使用了状态机,其目的是为了节省Continuation对象的创建,可以借鉴学习


关于我


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


作者:小黑不黑
来源:juejin.cn/post/7294852698460373004

0 个评论

要回复文章请先登录注册