美文网首页kotlinKotlin
Kotlin中的协程 - suspend

Kotlin中的协程 - suspend

作者: 盛世光阴 | 来源:发表于2021-07-13 23:04 被阅读0次

    前言

    Kotlin是一种在Java虚拟机上运行的静态类型编程语言,被称之为Android世界的Swift,在GoogleI/O2017中,Google宣布Kotlin成为Android官方开发语言

    delay

    delay是一个顶级函数,由于它被suspend修饰,所以只能用在协程或者被其他suspend函数修饰,它的功能为

    将当前协程延迟一个给定时间,但是不会阻塞当前线程 并且它也是可以被取消的

    public suspend fun delay(timeMillis: Long) {
        if (timeMillis <= 0) return // don't delay
        return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
    

    我们再看下另一个会延迟的函数Thread.sleep()它会导致 当前线程进行休眠

        /**
         * Causes the currently executing thread to sleep (temporarily cease
         * execution) for the specified number of milliseconds plus the specified
         * number of nanoseconds, subject to the precision and accuracy of system
         * timers and schedulers. The thread does not lose ownership of any
         * monitors.
         */
        public static void sleep(long millis, int nanos)
    

    在协程中使用它们,会有不同的效果,如果我们的协程都在同一个线程

    fun test() {
        MainScope().launch {
            Log.e("Mike","Coroutine1 start")
            delay(2000)
            Log.e("Mike","Coroutine1 end")
        }
    
        MainScope().launch {
            Log.e("Mike","Coroutine2 start")
            delay(2000)
            Log.e("Mike","Coroutine2 end")
        }
    }
    

    上述的代码在使用delay时打印,当前线程没有阻塞会执行Coroutine2

    Coroutine1 start
    Coroutine2 start
    //两秒后
    Coroutine1 end
    Coroutine2 end
    

    上述的代码在使用Thread.sleep()时打印

    Coroutine1 start
    //两秒后
    Coroutine1 end
    Coroutine2 start
    //两秒后
    Coroutine2 end
    

    很容易看到这两种用法的区别,并且当我们在协程中时如果使用了阻塞线程的Thread.sleep()也会有警告提示Inappropriate blocking method call 提示你使用了不适当的阻塞方法,因为线程阻塞会导致其他协程无法执行,会影响其他协程,delay表示的是非阻塞调用,不会阻塞当前线程

    非阻塞式挂起

    我们很容易理解阻塞与非阻塞的区别,从行为上来讲,就是是否挡住了你这条线程的后续执行,如果挡住了就是阻塞,没有挡住就是非阻塞,那么挂起是什么,挂起的行为其实就是切换了线程的工作

        MainScope().launch {
            delay(2000)
        }
    
        MainScope().launch {
            delay(2000)
        }
    

    在上述例子中,第一个协程创建完成之后就会被挂起,主线程的执行由当前协程切换到了下一个协程,当挂起两秒之后再将主线程切换回了第一个协程继续工作,挂起其实也就是一种协程的暂停行为,不会线程中的其他单元

    suspend

    suspend是协程中很重的关键字,它用来修饰函数,表示此函数是一个会挂起的函数,并且 挂起函数只有在协程中使用或者被另一个挂起函数调用,可以暂停和进行恢复,什么情况下需要用到挂起函数

    • 线程切换,挂起本身是线程切换不同的协程去工作,所以当需要进行线程切换时可以使用挂起函数
    • 延时,暂停往往代表在等待一些结果,当我们在等待一些返回结果时,协程可以通过挂起的方式等待,而不阻塞线程

    suspend只是对函数的一个标识别,它不像inline,refied等关键字一样会对代码造成影响,而是提醒使用者这是一个挂起函数,具体的挂起业务还是需要函数内部自己实现

    withContext

    withContext是一个挂起函数,表明它只能在协程或者其他suspend函数调用

    /**
     * Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns
     * the result.
     *
     * The resulting context for the [block] is derived by merging the current [coroutineContext] with the
     * specified [context] using `coroutineContext + context` (see [CoroutineContext.plus]).
     * This suspending function is cancellable. It immediately checks for cancellation of
     * the resulting context and throws [CancellationException] if it is not [active][CoroutineContext.isActive].
     *
     * This function uses dispatcher from the new context, shifting execution of the [block] into the
     * different thread if a new dispatcher is specified, and back to the original dispatcher
     * when it completes. Note that the result of `withContext` invocation is
     * dispatched into the original context in a cancellable way, which means that if the original [coroutineContext],
     * in which `withContext` was invoked, is cancelled by the time its dispatcher starts to execute the code,
     * it discards the result of `withContext` and throws [CancellationException].
     */
    public suspend fun <T> withContext
    

    需要传入一个suspending 代码块,并且基于合并后的Context执行环境,并且可以被取消,会返回代码块的执行结果,在suspending 代码块执行完毕之后有切换回来

    fun test() {
        MainScope().launch {
            Log.e("Mike", "Coroutine start")
            val result = withContext(Dispatchers.IO) {
                delay(2000)
                "resposne data"
            }
            Log.e("Mike", "Coroutine end $result")
        }
    }
    打印结果
    Coroutine start 主线程
    两秒后
    Coroutine end resposne data 主线程
    

    可以提取出suspend函数,这样我们的代码看起来是同步单线程执行的,但是实际却在不同的线程

    fun test() {
        MainScope().launch {
            Log.e("Mike", "Coroutine start") //主线程
            val result = getData() //getData在IO线程
            Log.e("Mike", "Coroutine end $result") //主线程
        }
    }
    
    suspend fun getData() = withContext(Dispatchers.IO) {
        delay(2000)
        "resposne data"
    }
    

    suspend使用案例

    文件读取

    private val scope = CoroutineScope(Dispatchers.Main)
    
    scope.launch {
        Toast.makeText(this@MainActivity, "start...", Toast.LENGTH_SHORT).show()
        val result = readFile()
        Toast.makeText(this@MainActivity, "end..." + result, Toast.LENGTH_LONG).show()
    }
    
    suspend fun readFile() = withContext(Dispatchers.IO) {
        val inp = assets.open("a.txt")
        val isr = InputStreamReader(inp)
        isr.readText().also {
            inp.close()
            isr.close()
        }
    }
    
    

    网络请求

    引入Okhttp

    implementation("com.squareup.okhttp3:okhttp:4.9.0")
    implementation("com.squareup.okhttp3:logging-interceptor:4.9.0")
    

    封装OkhttpClient

    object OkHttpManager {
    
        fun getOkhttpClient() = OkHttpClient.Builder()
            .addNetworkInterceptor(HttpLoggingInterceptor().apply {
                level = HttpLoggingInterceptor.Level.BODY
            })
            .build()
    }
    

    封装Sevice

    class RequestService(val okhttpClient: OkHttpClient) {
    
        suspend fun getInfo() = withContext(Dispatchers.IO) {
            val url = "https://www.baidu.com".toHttpUrl().newBuilder().build()
            val request = Request.Builder().url(url).build()
            okhttpClient.newCall(request).execute().body?.string()
        }
    }
    

    发起调用

    scope.launch {
        Toast.makeText(this@MainActivity, "start...", Toast.LENGTH_SHORT).show()
        val result = RequestService(OkHttpManager.getOkhttpClient()).getInfo()
        Toast.makeText(this@MainActivity, "end..." + result, Toast.LENGTH_LONG).show()
    }
    

    欢迎关注Mike的简书

    Android 知识整理

    相关文章

      网友评论

        本文标题:Kotlin中的协程 - suspend

        本文链接:https://www.haomeiwen.com/subject/uqqspltx.html