suspend他并不能切换线程,切换线程的是内部自带的suspend函数,ex. withContext
coroutine只是能用阻塞写法写出非阻塞代码,本质和thread是一样的
刚接触coroutine的人应该都会遇到这个名词,挂起函数,蛤?
挂起?挂起谁,挂在哪里,什麽时候挂起,让我一个一个回答
我们前面讲过launch/ async实际上做了甚麽,将任务post到thread里面,对吧?
那今天如果任务是挂起的呢? 那就挂起呀,以范例来说,Main thread执行到suspendThis()的时候,就会结束我们post过去的任务,去做他原本该做的事
ㄟ等等,但我们的任务还没做完呀?
不急不急,听我接着讲,任务结束,是对Main thread来说结束,而任务本身会透过withContext继续在IO thread执行,还记得我们说过withContext的特性吧,暂时切换thread,然後再切回去,没错,任务结束後,withContext又会自动帮我们切回去,而这所谓的自动切回,就是coroutine会再帮我们post一个任务,让我们回到原先的thread继续执行
所谓的挂起函数,就是稍後会被自动切回来的thread切换。 by扔物线
val scope = CoroutineScope(rootJob)
scope.launch {
suspendThis()
Log.i("","")
}
//变成
handler.post{
suspendThis()
Log.i("","")
}
suspend fun suspendThis(){
withContext ( Dispatchers.IO ){
// io task
}
}
随便找了一张图,但我不打算讲thread,只是给你看刚刚讲到的东西
图源
那要suspend干吗? 他又不负责切线程,拿掉不行吗? 诶~真不行,它的用处,现在才要开始
suspend是个coroutine很常见的关键字,几乎到处都能看到他的身影,标记了suspend的方法,一定要在coroutine或另外的suspend内使用,当调用了supend方法,会暂停当前coroutine的执行,并保留所有局部变量,并在结束後resume,并执行之後的code
suspend — pause the execution of the current coroutine, saving all local variables
resume — continue a suspended coroutine from the place it was paused
在语法方面,suspend本身是提醒开发者,这项任务需要耗时,或是切换thread,请要coroutine里面适当的调用我,而真正耗时的部分是suspend fun里面的code
这个提醒,有用吗?大有用处,我们自己都有可能忘记某个fun是耗时的,更不用说,如果你用了一个package,你也不知道他的代码是耗时的呀,一不小心,ui卡一下,又要通灵抓bug了,那如果有提醒的话呢?ide会告诉方法的调用者,我是耗时任务,在coroutine里面调用我
suspend方法并不会让kotlin在後台执行函数,在主线程使用suspend或是启动协程是相当常见的,而我们应该使用withContext(),或其他方式确保主线程安全
前面讲过callback没有不见,而是编译器透过finite state machine将suspend fun转换为callback的版本
TL;DR; The Kotlin compiler will create a state machine for every suspend function that manages the coroutine’s execution for us!
我们切线程再切回来,有个关键字叫resume,用中文理解一下就是恢复状态
而他怎麽恢复状态呢? 他是透过coroutine的 Continuation ,来达到恢复状态,这也是为甚麽suspend函数只能在coroutineScope或另一个suspend里面执行,因为要用coroutine才能达到恢复状态,对吧?
那Continuation 又是甚麽? 官方解释说他是带额外讯息的回调接口,source code长这样
interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(value: Result<T>)
}
看这里了解CoroutineContext
resumeWith,用Result回复coroutine的执行,可能包含执行结果或是Exception
那suspend编译後长怎样
fun loginUser(userId: String, password: String, completion: Continuation<Any?>) {
val user = userRemoteDataSource.logUserIn(userId, password)
val userDb = userLocalDataSource.logUserIn(user)
completion.resume(userDb)
}
这里的completion非常重要,他是用来将suspend 的结果回传给调用他的coroutine,但这只是简化版的code
直接跟官方blog借code,我有附连结在下面,蛮建议去读的,这边我就简单带一下概念而已
suspend透过Continuation在不同suspend切换thread之间,传递value,并且透过转型将Continuation转换成 StateMachine 类别,利用label确定执行顺序,如果是第一次执行fun,会建立State machine,之後每次都会将State Machine作为参数传递,递回呼叫loginUser function
直到最後,透过resume回传了userDb,可以对应上面的code
/* Copyright 2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 */
fun loginUser(userId: String?, password: String?, completion: Continuation<Any?>) {
class LoginUserStateMachine(
// completion parameter is the callback to the function that called loginUser
completion: Continuation<Any?>
): CoroutineImpl(completion) {
// objects to store across the suspend function
var user: User? = null
var userDb: UserDb? = null
// Common objects for all CoroutineImpl
var result: Any? = null
var label: Int = 0
// this function calls the loginUser again to trigger the
// state machine (label will be already in the next state) and
// result will be the result of the previous state's computation
override fun invokeSuspend(result: Any?) {
this.result = result
loginUser(null, null, this)
}
}
val continuation = completion as? LoginUserStateMachine ?: LoginUserStateMachine(completion)
when(continuation.label) {
0 -> {
// Checks for failures
throwOnFailure(continuation.result)
// Next time this continuation is called, it should go to state 1
continuation.label = 1
// The continuation object is passed to logUserIn to resume
// this state machine's execution when it finishes
userRemoteDataSource.logUserIn(userId!!, password!!, continuation)
}
1 -> {
// Checks for failures
throwOnFailure(continuation.result)
// Gets the result of the previous state
continuation.user = continuation.result as User
// Next time this continuation is called, it should go to state 2
continuation.label = 2
// The continuation object is passed to logUserIn to resume
// this state machine's execution when it finishes
userLocalDataSource.logUserIn(continuation.user, continuation)
}
2 -> {
// Checks for failures
throwOnFailure(continuation.result)
// Gets the result of the previous state
continuation.userDb = continuation.result as UserDb
// Resumes the execution of the function that called this one
continuation.cont.resume(continuation.userDb)
}
else -> throw IllegalStateException(...)
}
}
大家都说,coroutine的挂起是非阻塞式的,真有那麽神奇的黑魔法吗?
他的非阻塞式,是指不卡thread
在我们学coroutine的漫漫长路里,一定会有文章说,coroutine是非阻塞式,thread是阻塞式,对,但也不对,因为他没讲明白,记得一点,kotlin 的coroutine是线程框架,它的本质是一样的,thread的切换也是非阻塞式
那为什麽又说他对呢?
以单个thread来说,耗时任务是阻塞式的,那一单个coroutine来说呢,它可以是非阻塞式的,因为他能用suspend来切换线程,懂了吗? coroutine是切换thread来达到非阻塞,那能不能用thread写非阻塞代码,当然可以,因为要做的事都一样,就是切换thread
coroutine只是能用阻塞写法写出非阻塞代码
以开发时间来看,有的
以程序执行来看,没有
为甚麽呢,不是说挂起後thread就能执行其他任务吗? 这样不就不用痴痴等待
复习一下前面的概念,任务执行是cpu的工作,今天我们要执行io请求,要切到io thread对吧?
注意,一个耗时任务,是会慢慢执行,而不是在某个时间点突然完成,以前面范例来说,我们让main thread挂起耗时任务去执行其他任务,为甚麽? 因为在main执行耗时任务ui会freeze,会ANR对吧?
那任务不做了吗? 要做呀,只是我们拿到io thread执行了呀,要做的事情一件都没有少
那suspend不是能挂起吗? 那在io thread挂起,不就可以提升thread的利用率,这种想法很诱人,却很误导,也非常危险
前面我们讲了什麽? 挂起的任务是对当前的thread来说,这个coroutine结束了,并且在suspend function执行完毕後,切回原本的thread,往里面post之後的任务,那是不是还是要有thread去完成任务,毕竟他不会通灵,也不会自己完成呀
那能不能在一个thread里面做并发呢?
io/ default可以有复数的thread,os会从thread pool拿出thread来执行任务,任务完成後要嘛回收要嘛再利用; 还有一点,async的并发会创造新的coroutine,是透过不同coroutine在不同 thread同时做多个任务,如果你的并发把耗时任务丢到Main thread的话,他是会照顺序完成的。
>>: D3JsDay09 资料元素来绑定,让你元素有内定—资料绑定
今年的疫情蛮严重的,希望大家都过得安好,希望疫情快点过去,能回到一些线下技术聚会的时光~ 今天目标:...
绘制矩形 核心 先来学习绘制矩形的方法 strokeRect 使用当前的绘画样式,描绘一个起点在 (...
接续前次实作. 由於资料转换需要透过一个 instance 运作, 先建立 Replication ...
使用Emmet省下泡一杯咖啡的时间 (HTML篇) 效率满点的好工具 为什麽而学? 前身是Zen c...
接下来,会就六角学院 UI 设计入门 课程中,针对团队合作时会碰到的情境稍作讨论。 设计稿再好看也没...