一 挂起
suspend代表挂起,从当前线程挂起,换句话说,就是这个协程从执行他的线程上脱离
看下以下代码:
viewModelScope.launch {
val resp = repo.uploadOss(bitmap)
Log.d("", resp.toString())
}
suspend inline fun uploadOss(bitmap : Bitmap) : NetResult<OssBean> {
return withContext(Dispatchers.IO) {
...
}
}
这段代码是指的往你的主线程post
一个Runnable
,这个Runnable
就是你的协程代码:
handler.post {
val resp = repo.uploadOss(bitmap)
Log.d("", resp.toString())
}
执行在哪个线程
我们来看以下两段代码:
viewModelScope.launch {
startLoading()
LogUtils.d("", "thread launch:${Thread.currentThread().name}" )
val data = repo.requestHttp()
endLoading()
}
fun requestHttp() : Response {
LogUtils.d("", "thread suspend:${Thread.currentThread().name}" )
val response = call()
return response
}
打印出来:
thread launch:main @coroutine#1
thread suspend:main @coroutine#1
main
: 表示这个协程的名称或入口函数是 main。
@coroutine#1
: 表示这是第一个协程实例,#1 是一个标识符,用于区分不同的协程实例。
将代码稍微修改下:
fun requestHttp() : Response {
val response = withContext(Dispatchers.IO) {
LogUtils.d("fjb", "thread suspend:${Thread.currentThread().name}" )
call()
}
return response
}
打印出来:
thread launch:main @coroutine#1
thread suspend:DefaultDispatcher-worker-1 @coroutine#1
所以,suspend并没有切换线程,真正切换线程需要withContext
挂起函数在执行完成之后,协程会重新切回它原先的线程。
协程又是如何切换回来的
它通过一个叫 Continuation 的接口的实例来返回结果:
public interface Continuation<in T> {
public val context: CoroutineContext
public fun resumeWith(result: Result<T>)
}
举例,以下代码:
GlobalScope.launch(Dispatchers.Main) {
try {
//showUser 在 await 的 Continuation 的回调函数调用后执行
showUser(api.getUser().await())
} catch (e: Exception) {
showError(e)
}
await方法定义:
public suspend fun await(): T
但在虚拟机里,他的真实签名是:
kotlinx/coroutines/Deferred.await (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
以上例子代码相当于:
//注意以下不是正确的代码,仅供大家理解协程使用
GlobalScope.launch(Dispatchers.Main) {
api.getUser().await(object: Continuation<User>{
override fun resume(value: User) {
showUser(value)
}
override fun resumeWithException(exception: Throwable){
showError(exception)
}
})
}
await相当于如下实现:
fun await(continuation: Continuation<User>): Any {
try {
val user = ...
handler.post{ continuation.resume(user) }
} catch(e: Exception) {
handler.post{ continuation.resumeWithException(e) }
}
}
二。使用时机
协程中有luanch和async两个函数
我们来看下以下两种获取网络结果的方式:
第一种
viewModelScope.launch {
startLoading()
val resp = repo.uploadOss(bitmap)
Log.d("", resp.toString())
endLoading
}
第二种
viewModelScope.launch {
startLoading()
val def: Deferred<NetResult<OssBean>> = async {
repo.uploadOss(bitmap)
}
Log.d("", def.await().toString())
endLoading
}
第一种:
调用 repo.uploadOss(bitmap)
时,协程会挂起,直到该函数完成并返回结果。然后,结果会被赋值给resp
变量,并打印到日志中。
第二种:
async
会启动一个新的协程并返回一个 Deferred
对象。Deferred
对象代表一个将来会返回结果的任务。通过调用 def.await()
,当前协程会挂起,直到 async
块中的任务完成并返回结果。然后,结果会被打印到日志中。
结论:
在大多数情况下,两种方式的效果是相同的,但 async 方式更适合需要并行执行多个异步任务的场景,因为它可以启动多个并行任务并等待它们的结果。
比如:
viewModelScope.launch {
startLoading()
val def: Deferred<NetResult<OssBean>> = async {
repo.uploadOss(bitmap)
}
val count: Deferred<NetResult<Int>> = async {
repo.countOss()
}
Log.d("", "上传结果${def.await().toString()}, 现在一共有${count.await().toString()}")
endLoading
}
网友评论