Kotlin协程知识点概述

        Kotlin 是⼀⻔仅在标准库中提供最基本底层 API 以便各种其他库能够利⽤协程的语⾔。与许多其他具有类似功能的语⾔不同,async 与 await 在 Kotlin 中并不是关键字,甚⾄都不是标准库的⼀部分。此外,Kotlin 的 挂起函数概念为异步操作提供了⽐ future 与 promise 更安全、更不易出错的抽象。

        kotlinx.coroutines 是由 JetBrains 开发的功能丰富的协程库。它包含本指南中涵盖的很多启⽤⾼级协程的原语,包括 launch 、async 等等。

一.基础:

        本质上,协程是轻量级的线程。它们在某些 CoroutineScope 上下⽂中与 launch 协程构建器 ⼀起启动。这⾥我们在 GlobalScope 中启动了⼀个新的协程,这意味着新协程的⽣命周期只受整个应⽤程序的⽣命周期限制。

import kotlinx.coroutines.*

fun main() {
    GlobalScope.launch { // 在后台启动⼀个新的协程并继续
        delay(1000L) // ⾮阻塞的等待 1 秒钟
        println("World!") // 在延迟后打印输出
    }
//    thread {
        // Suspend function 'delay' should be called only
        // from a coroutine or another suspend function
//        delay(1000)
//    }

    println("Hello,") // 协程已在等待时主线程还在继续
    Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}

fun main() = runBlocking<Unit> {
    GlobalScope.launch {
        delay(1000)
        println("World")
    }
    println("Hello,")
    delay(2000)
}

 等待一个作业:

        延迟⼀段时间来等待另⼀个协程运⾏并不是⼀个好的选择。显式(以⾮阻塞⽅式)等待所启动的后台job执⾏结束。

fun main() = runBlocking<Unit> {
    val job = GlobalScope.launch {
        delay(1000)
        println("World")
    }
    println("Hello,")
    job.join()
}

结构化的并发:

        在执⾏操作所在的指定作⽤域内启动协程,⽽不是像通常使⽤线程(线程总是全局的)那样在GlobalScope中启动。

fun main() = runBlocking<Unit> {
    //启动launch协程,无需显式join。外部协程runBlocking直到其
    //作用域中启动的所有协程都执行完毕后才会结束。
    launch {
        delay(1000)
        println("World")
    }
    println("Hello,")
}

作用域构建器:

        除了由不同的构建器提供协程作⽤域之外,还可以使⽤coroutineScope构建器声明自己的作用域。它会创建⼀个协程作⽤域并且在所有已启动⼦协程执⾏完毕之前不会结束。runBlocking与coroutineScope很类似,都是等待其协程体以及所有子协程结束。主要区别在于runBlocking方法会阻塞当前线程来等待,而coroutineScope只是挂起,会释放底层线程用于其他用途。runBlocking是常规函数,coroutineScope是挂起函数。

fun main() = runBlocking<Unit> {
    launch {
        delay(200)
        println("Task from runBlocking")
    }
    coroutineScope {
        launch {
            delay(500)
            println("Task from nested launch")
        }
        delay(100)
        println("Task from coroutine scope")
    }
    println("Coroutine scope is over")
}

提取函数重构:

        将launch{}内部的代码块提取到独立的函数中。提取函数时需加上suspend修饰符及成为挂起函数。

fun main() = runBlocking<Unit> {
    launch {
        doWorld()
        println("Hello,")
    }
}

suspend fun doWorld() {     //挂函数
    delay(1000)
    println("World!")
}

全局协程像守护线程:

        启动的活动协程并不会使进程保活。它们就像守护线程。

fun main() = runBlocking {
    GlobalScope.launch {
        //大于主线程中的1.3s,只会执行到2次,
        // 第三次随着主线程中的结束而结束循环。
        repeat(1000) { i ->
            delay(500)
            println("I'm sleeping $i")  
        }
    }
    delay(1300)
}

二.取消与超时:

取消协程的执行:

        在⼀个⻓时间运⾏的应⽤程序中,也许需要对后台协程进⾏细粒度的控制 

fun main() = runBlocking {
    val job = launch {
        repeat(10) { i ->
            delay(500)
            println("I'm sleeping $i")
        }
    }
    delay(1300) //延迟一段时间
    println("main: I`m tred of wating!")
    //⼀旦 main 函数调⽤了 job.cancel ,我们在其它的协程中就看不到任何输出,因为它被取消了
    job.cancel()          //如cancel,主线程的1.3s过后job会结束执行
    job.join()            //如只join的话,主线程的1.3s过后job会在一直执行到结束
//    job.cancelAndJoin()
    println("main: Now I can quit!")
}

取消时协作的:

        协程的取消是协作的。⼀段协程代码必须协作才能被取消。所有 kotlinx.coroutines 中的挂起函数都是可被取消的 。如果协程正在执⾏计算任务,并且没有检查取消的话,那么它是不能被取消的。

fun main() = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 5) {     //直到打印5次后才结束
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("Job: I`m sleeping ${i++} ...")
                nextPrintTime += 1000
            }
        }
    }
    delay(1300)
    println("main: I`m tred of wating!")
    job.cancelAndJoin()
    println("main: Now I can quit!")
}

使计算代码可取消:

        有两种⽅法来使执⾏计算的代码可以被取消,第⼀种⽅法是定期调⽤挂起函数来检查取消。对于这种目的的yield是个好选择。

fun main() = runBlocking {
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (isActive) {     //isActive使计算可取消
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("Job: I`m sleeping ${i++} ...")
                nextPrintTime += 1000
            }
        }
    }
    delay(1300)
    println("main: I`m tred of wating!")
    job.cancelAndJoin()
    println("main: Now I can quit!")
}

在finally中释放资源:

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(10) { i ->
                delay(500)
                println("Job: I`m sleeping $i ...")
            }
        } catch (e: Exception) {
            //在被取消的协程中 CancellationException 被认为
            //是协程执⾏结束的正常原因
            //会在跟随着主线程结束而抛出CancellationException异常
            println("Exception:" + e)
        } finally {
            println("job: I'm running finally")
        }
    }
    delay(1300) //延迟一段时间
    println("main: I`m tred of wating!")
    job.cancelAndJoin()
    println("main: Now I can quit!")
}

运行不能取消的代码块:

        任何尝试在 finally 块中调⽤挂起函数的⾏为都会抛出CancellationException,因为这里持续运行的代码是可以被取消的。通常,这并不是一个问题,所有良好的关闭操作(关闭一个文件、取消一个作业、或是关闭任何一种通信通道)通常都是非阻塞的,并且不会调用任何挂起函数。当需要挂起一个被取消的协程,可以把代码包装在withConext(NonCancellable){}中。   

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(10) { i ->
                delay(500)
                println("Job: I`m sleeping $i ...")
            }
        } catch (e: Exception) {
            println("Exception:" + e)
        } finally {
            //包装挂起函数,避免抛出异常
            withContext(NonCancellable) {
                println("job: I'm running finally")
                delay(1000)     //包装withContext中能执行挂起函数
                println("job: And I've just delayed for 1 sec because I'm non-cancellable")
            }

        }
    }
    delay(1300) //延迟一段时间
    println("main: I`m tred of wating!")
    job.cancelAndJoin()
    println("main: Now I can quit!")
}

超时:

        绝⼤多数取消⼀个协程的理由是它有可能超时。

fun main() = runBlocking {
    //使用withTimeout会使超时抛出异常
    withTimeout(1300) {
        repeat(10) { i ->
            delay(500)
            println("Job: I`m sleeping $i ...")
        }
    }
}

fun main() = runBlocking {
    //使用withTimeoutOrNull不会使超时抛出异常,超时会返回null
    val result = withTimeoutOrNull(1300) {
        repeat(10) { i ->
            delay(500)
            println("Job: I`m sleeping $i ...")
        }
        "Done"
    }
    println("Result is $result")
}

超时和资源共享的异步:

        在withtTmeout时异步的,要特别关注的是此代码块内的资源释放及流关闭是否成功的问题。

var acquired = 0
class Resource {
    init {
        acquired++
    }

    fun close() {
        acquired--
    }
}
fun main() = runBlocking {
    repeat(100_0000) { i ->
        launch {
            val resource = withTimeout(60) {
                delay(59)  //withtTmeout是异步的超时后,导致内存泄漏acquired不一定为0
                Resource()
            }
            resource.close()
        }
    }
    //非0,有内存泄漏
    //0代表资源都释放了,没有内存泄漏
    println(acquired)
}


fun main() = runBlocking {
    repeat(100_0000) { i ->
        launch {
            val resource: Resource? = null
            try {
                withTimeout(60) {
                    delay(59)  //withtTmeout是异步的超时后,导致内存泄漏acquired不一定为0
                    Resource()
                }
            } finally {
                resource?.close()       //在finally中确保资源的释放
            }
        }
    }
    println(acquired)
}

三.组合挂起函数:

默认顺序调用:

        使⽤普通的顺序来进⾏调⽤,因为这些代码是运⾏在协程中的,只要像常规的代码⼀样顺序 都是默认的。

suspend fun doSomethingUsefullOne(): Int {
    delay(1000)
    return 13
}

suspend fun doSomethingUsefullTwo(): Int {
    delay(1000)
    return 42
}

fun main() = runBlocking {
    //顺序调用并计算时间
    val time = measureTimeMillis {
        val one = doSomethingUsefullOne()       
        val two = doSomethingUsefullTwo()
        println("The answer is ${one + two}")
    }
    println("Completed in $time ms")
}

使用async并发:

        在概念上,async类似于launch。启动了一个单独的协程,这是一个轻量级的线程并与其他所有的协程一起并发工作。不同之处在于launch返回一个job并不附带任何的结果值,而async返回一个Deferred——一个轻量级的非阻塞future,这代表一个将会稍后提供结果的promise。可以使⽤ .await() 在⼀个延期的值上得到它的最终结果,但是 Deferred 也是⼀个 Job ,所以如果需要的话ÿ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值