Kotlin学习手记——协程初步
Posted 川峰
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin学习手记——协程初步相关的知识,希望对你有一定的参考价值。
乍一看很懵逼,其实简单的说,协程就是可以控制线程切换的代码,或能实现类似的功能的,可以让异步代码同步化,如JS里的async
和await
等。 这种方式可以给我们简化代码,使得业务逻辑清晰。
最常见于网络请求的回调,如一般我们会有一个成功的回调和一个失败的回调,按照异步的写法,两个回调方法中都要处理逻辑,协程可以使异步回调按照同步的方式去返回结果,写法简单,清晰。
kotlin通过suspend关键字来指定函数可以挂起,实现异步转同步的功能。
异步程序最麻烦的就是,当你有多个异步程序同时运行时,比如同时并发请求了好几个接口,要想拿到返回值一并处理,就比较困难。。还有就是如果我想按照顺序的异步执行,通常也比较困难
协程本身的概念实际包含了线程调度的概念,只有能控制线程切换,才有可能实现上面的功能。
在参考的学习资料当中,作者将协程和线程做了比较,其实我觉得这里不太适合,但还是把这两个图抠出来了,线程其实是操作系统的概念,而协程则属于编程语言的范畴,它是应用程序层的api层的东西,二者其实没什么太大关系。
值得一提的是协程并不是kotlin里面特有的东西,其他语言如Python、JS等也要类似的语法可以实现。
比较常见的可能就是async
和await
关键字,JS里面比较常见,但是这玩意居然在C#和Python里面也有
js需要在使用的时候添加这俩关键字,kotlin就比较优秀了,使用的时候感觉不出来,就像完全的同步代码一样,只不过它是定义的时候写好了罢了。
kotlin实现恢复挂起点是通过一个接口类Continuation
(英文翻译过来叫"继续")来实现的
public interface Continuation<in T>
/**
* The context of the coroutine that corresponds to this continuation.
*/
public val context: CoroutineContext
/**
* Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: Result<T>)
它有一个上下文对象,还有一个resumeWith
方法,这个方法就是用来恢复挂起函数用的,如果是成功和失败回调都可以通过result参数来返回。
添加了suspend
关键字的函数,kotlin最终生成下面的方法:
可以看到,suspend
函数实际上需要一个continuation参数
如果挂起函数没有真正被挂起(没有发生线程切换)返回值返回的就是实际参数类型,否则返回的是一个标记。
suspend fun getUserSuspend(name: String) = suspendCoroutine<User> continuation ->
githubApi.getUserCallback(name).enqueue(object: Callback<User>
override fun onFailure(call: Call<User>, t: Throwable) =
continuation.resumeWithException(t)
override fun onResponse(call: Call<User>, response: Response<User>) =
response.takeIf it.isSuccessful ?.body()?.let(continuation::resume)
?: continuation.resumeWithException(HttpException(response))
)
suspend fun main()
val user = getUserSuspend("bennyhuo")
showUser(user)
最简单的复写挂起函数的回调:
suspend fun suspendFunc() = suspendCoroutine<Int>
it.resumeWith(Result.success(1))
只不过真正的挂起需要真正的切换线程,如果直接调用的话相当于没有挂起。
suspend
.createCoroutine(object: Continuation<Unit> //创建协程
override val context = EmptyCoroutineContext
override fun resumeWith(result: Result<Unit>)
log("Coroutine End with $result")
).resume(Unit) //恢复
suspend
.startCoroutine(object: Continuation<Unit> //启动协程
override val context = EmptyCoroutineContext
override fun resumeWith(result: Result<Unit>)
log("Coroutine End with $result") //协程执行完后调用
)
简单的例子,异步转同步,先使用retrofit的api创建一个接口请求实例:
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Call
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.GET
import retrofit2.http.Path
val githubApi by lazy
val retrofit = retrofit2.Retrofit.Builder()
.client(OkHttpClient.Builder().addInterceptor(Interceptor
it.proceed(it.request()).apply
log("request: $code()")
).build())
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
retrofit.create(GitHubApi::class.java)
interface GitHubApi
@GET("users/login")
fun getUserCallback(@Path("login") login: String): Call<User>
@GET("users/login")
suspend fun getUserSuspend(@Path("login") login: String): User
data class User(val id: String, val name: String, val url: String)
常见的调用场景:
import com.bennyhuo.kotlin.coroutinebasics.api.User
import com.bennyhuo.kotlin.coroutinebasics.api.githubApi
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
//普通的异步请求,成功和失败需要分开在两个回调函数中处理
fun async()
val call = githubApi.getUserCallback("bennyhuo")
call.enqueue(object : Callback<User>
override fun onFailure(call: Call<User>, t: Throwable)
showError(t)
override fun onResponse(call: Call<User>, response: Response<User>)
response.body()?.let(::showUser) ?: showError(NullPointerException())
)
//for循环中发起多个异步请求,获取请求结果
fun asyncLoop()
val names = arrayOf("abreslav","udalov", "yole")
names.forEach name ->
val call = githubApi.getUserCallback(name)
call.enqueue(object : Callback<User>
override fun onFailure(call: Call<User>, t: Throwable)
showError(t)
override fun onResponse(call: Call<User>, response: Response<User>)
response.body()?.let(::showUser) ?: showError(NullPointerException())
)
//使用挂起函数来请求,不需要请求的回调
suspend fun coroutine()
val names = arrayOf("abreslav","udalov", "yole")
names.forEach name ->
try
val user = githubApi.getUserSuspend(name) //请求后这里会挂起,直到请求成功之后恢复执行
showUser(user)
catch (e: Exception)
showError(e)
//通过挂起函数的方式获取所有异步请求的结果放到一个数组当中
suspend fun coroutineLoop()
val names = arrayOf("abreslav","udalov", "yole")
val users = names.map name ->
githubApi.getUserSuspend(name)
实例:模仿Python的序列生成器Generator
import kotlin.coroutines.*
interface Generator<T>
operator fun iterator(): Iterator<T>
class GeneratorImpl<T>(private val block: suspend GeneratorScope<T>.(T) -> Unit, private val parameter: T): Generator<T>
override fun iterator(): Iterator<T>
return GeneratorIterator(block, parameter)
//密封类的使用 定义状态
sealed class State
//continuation作为参数方便下次调用
class NotReady(val continuation: Continuation<Unit>): State()
class Ready<T>(val continuation: Continuation<Unit>, val nextValue: T): State()
object Done: State()
//GeneratorScope<T>.(T) 点左边的是receiver
class GeneratorIterator<T>(private val block: suspend GeneratorScope<T>.(T) -> Unit, override val parameter: T)
: GeneratorScope<T>(), Iterator<T>, Continuation<Any?>
override val context: CoroutineContext = EmptyCoroutineContext
private var state: State
init
val coroutineBlock: suspend GeneratorScope<T>.() -> Unit = block(parameter) //挂起函数 调用block lambda表达式传入parameter参数
val start = coroutineBlock.createCoroutine(this, this) //不需要马上启动的话使用createCoroutine创建协程
state = State.NotReady(start) //初始状态肯定是NotReady,createCoroutine返回的start参数就是Continuation类型
println("init====================")
//yield是一个挂起函数 覆写GeneratorScope类的方法
override suspend fun yield(value: T) = suspendCoroutine<Unit>
continuation ->
println("yield========$state.javaClass.simpleName value=$value")
state = when(state)
is State.NotReady -> State.Ready(continuation, value) //调用yield(xx)方法使状态进入Ready状态
is State.Ready<*> -> throw IllegalStateException("Cannot yield a value while ready.")
State.Done -> throw IllegalStateException("Cannot yield a value while done.")
//这里continuation没有直接调用resume方法,在后面用户调用hasNext()或next()时调用resume()
private fun resume()
println("resume()====================")
//val currentState = state之后调用.continuation会自动类型转换
when(val currentState = state)
is State.NotReady ->
println("resume()====================when NotReady")
currentState.continuation.resume(Unit) // NotReady时调用Continuation的resume方法恢复挂起点继续执行
override fun hasNext(): Boolean
println("hasNext()====================")
resume()
return state != State.Done
//next方法返回yield存入的值
override fun next(): T
println("next()========$state.javaClass.simpleName")
return when(val currentState = state)
is State.NotReady ->
resume()
return next() //NotReady时调用下次的next
is State.Ready<*> ->
state = State.NotReady(currentState.continuation) //state状态流转
println("next()====return value")
(currentState as State.Ready<T>).nextValue //Ready时才取值返回
State.Done -> throw IndexOutOfBoundsException("No value left.") //Done状态调用next()抛异常
//协程体执行完毕
override fun resumeWith(result: Result<Any?>)
println("resumeWith====================")
state = State.Done
result.getOrThrow()
//这个是定义一个receiver类,保证yield()方法只能在lambda表达式的大括号内使用
abstract class GeneratorScope<T> internal constructor()
protected abstract val parameter: T
abstract suspend fun yield(value: T)
//返回值具有迭代器功能
fun <T> generator(block: suspend GeneratorScope<T>.(T) -> Unit): (T) -> Generator<T>
return parameter: T ->
println("parameter = $parameter") // parameter = 10 这个是generator接收的start函数,nums(10)
GeneratorImpl(block, parameter)
fun main()
val nums = generator start: Int ->
for (i in 0..5)
yield(start + i) //yield会挂起函数调用处
val seq = nums(10)
//println(seq.iterator().next())
for (j in seq)
println(j)
//kotlin官方提供的sequence序列中有yield方法
val sequence = sequence Kotlin学习手记——Json解析