Kotlin学习手记——协程初步

Posted 川峰

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin学习手记——协程初步相关的知识,希望对你有一定的参考价值。


乍一看很懵逼,其实简单的说,协程就是可以控制线程切换的代码,或能实现类似的功能的,可以让异步代码同步化,如JS里的asyncawait等。 这种方式可以给我们简化代码,使得业务逻辑清晰。




最常见于网络请求的回调,如一般我们会有一个成功的回调和一个失败的回调,按照异步的写法,两个回调方法中都要处理逻辑,协程可以使异步回调按照同步的方式去返回结果,写法简单,清晰。


kotlin通过suspend关键字来指定函数可以挂起,实现异步转同步的功能。


异步程序最麻烦的就是,当你有多个异步程序同时运行时,比如同时并发请求了好几个接口,要想拿到返回值一并处理,就比较困难。。还有就是如果我想按照顺序的异步执行,通常也比较困难


协程本身的概念实际包含了线程调度的概念,只有能控制线程切换,才有可能实现上面的功能。





在参考的学习资料当中,作者将协程和线程做了比较,其实我觉得这里不太适合,但还是把这两个图抠出来了,线程其实是操作系统的概念,而协程则属于编程语言的范畴,它是应用程序层的api层的东西,二者其实没什么太大关系。




值得一提的是协程并不是kotlin里面特有的东西,其他语言如Python、JS等也要类似的语法可以实现。




比较常见的可能就是asyncawait关键字,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解析

day10:kotlin的协程已经安卓网络技术初步

Kotlin学习手记——反射

kotlin之协程(coroutines)学习

分享Android KTX + Kotlin协程 组合使用

Kotlin学习手记——注解注解处理器编译器插件