zoukankan      html  css  js  c++  java
  • Kotlin协程基础

    开发环境

    • IntelliJ IDEA 2021.2.2 (Community Edition)
    • Kotlin: 212-1.5.10-release-IJ5284.40

    我们已经通过第一个例子学会了启动协程,这里介绍一些协程的基础知识。

    阻塞与非阻塞

    runBlocking

    delay是非阻塞的,Thread.sleep是阻塞的。显式使用 runBlocking 协程构建器来阻塞。

    import kotlinx.coroutines.*
    
    fun main() {
        GlobalScope.launch { // 在后台启动一个新的协程并继续
            delay(200)
            "rustfisher.com".forEach {
                print(it)
                delay(280)
            }
        }
        println("主线程中的代码会立即执行")
        runBlocking {     // 这个表达式阻塞了主线程
            delay(3000L)  //阻塞主线程防止过快退出
        }
        println("
    示例结束")
    }
    

    可以看到,runBlocking里使用了delay来延迟。用了runBlocking的主线程会一直阻塞直到runBlocking内部的协程执行完毕。
    也就是runBlocking{ delay }实现了阻塞的效果。

    我们也可以用runBlocking来包装主函数。

    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        delay(100) // 在这里可以用delay了
    
        GlobalScope.launch {
            delay(100)
            println("Fisher")
        }
        print("Rust ")
        delay(3000)
    }
    

    runBlocking<Unit>中的<Unit>目前可以省略。

    runBlocking也可用在测试中

    // 引入junit
    dependencies {
        implementation("junit:junit:4.13.1")
    }
    

    单元测试

    使用@Test设置测试

    import org.junit.Test
    import kotlinx.coroutines.*
    
    class C3Test {
    
        @Test
        fun test1() = runBlocking {
            println("[rustfisher] junit测试开始 ${System.currentTimeMillis()}")
            delay(1234)
            println("[rustfisher] junit测试结束 ${System.currentTimeMillis()}")
        }
    }
    

    运行结果

    [rustfisher] junit测试开始 1632401800686
    [rustfisher] junit测试结束 1632401801928
    

    IDEA可能会提示no tasks available。需要把测试选项改为IDEA,如下图。

    test-setting.png

    等待

    有时候需要等待协程执行完毕。可以用join()方法。这个方法会暂停当前的协程,直到执行完毕。需要用main() = runBlocking

    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        println("[rustfisher]测试等待")
        val job1 = GlobalScope.launch {
            println("job1 start")
            delay(300)
            println("job1 done")
        }
        val job2 = GlobalScope.launch {
            println("job2 start")
            delay(800)
            println("job2 done")
        }
    
        job2.join()
        job1.join() // 等待
        println("测试结束")
    }
    

    运行log

    [rustfisher]测试等待
    job1 start
    job2 start
    job1 done
    job2 done
    测试结束
    

    结构化的并发

    GlobalScope.launch时,会创建一个顶层协程。之前的例子我们也知道,它不使用主线程。新创的协程虽然轻量,但仍会消耗一些内存资源。如果忘记保持对新启动的协程的引用,它还会继续运行。

    我们可以在代码中使用结构化并发。

    示例中,我们使用runBlocking协程构建器将main函数转换为协程。在里面(作用域)启动的协程不需显式使用join

    观察下面的例子:

    import kotlinx.coroutines.*
    
    fun main() = runBlocking<Unit> {
        println("主线程id ${Thread.currentThread().id}")
        launch { // 在 runBlocking 作用域中启动一个新协程1
            println("协程1所在线程id ${Thread.currentThread().id}")
            delay(300)
            println("协程1执行完毕")
        }
        launch { // 在 runBlocking 作用域中启动一个新协程2
            println("协程2所在线程id ${Thread.currentThread().id}")
            delay(500)
            println("协程2执行完毕")
        }
        println("主线程执行完毕")
    }
    

    运行log

    主线程id 1
    主线程执行完毕
    协程1所在线程id 1
    协程2所在线程id 1
    协程1执行完毕
    协程2执行完毕
    

    可以看到,不用像之前那样调用Thread.sleep或者delay让主线程等待一段时间,防止虚拟机退出。

    程序会等待它所有的协程执行完毕,然后真正退出。

    作用域构建器

    使用 coroutineScope 构建器声明自己的作用域。它会创建一个协程作用域,并且会等待所有已启动子协程执行完毕。

    runBlockingcoroutineScope 看起来类似,因为它们都会等待其协程体以及所有子协程结束。主要区别在于:

    • runBlocking 方法会阻塞当前线程来等待,是常规函数
    • coroutineScope 只是挂起,会释放底层线程用于其他用途,是挂起函数

    下面这个示例展示了作用域构建器的特点。main是一个作用域。

    import kotlinx.coroutines.*
    
    fun main() = runBlocking { // this: CoroutineScope
        launch {
            delay(200L)
            println("协程1 t${Thread.currentThread().id}")
        }
    
        coroutineScope { // 创建一个协程作用域
            launch {
                delay(500L)
                println("内部协程2-1 t${Thread.currentThread().id}")
            }
    
            delay(100L)
            println("协程2 t${Thread.currentThread().id}")
        }
    
        println("主任务完毕")
    }
    

    运行log

    协程2 t1
    协程1 t1
    内部协程2-1t1
    主任务完毕
    

    提取函数重构

    launch { …… } 内部的代码块提取到独立的函数中。提取出来的函数需要 suspend 修饰符,它是挂起函数

    import kotlinx.coroutines.delay
    import kotlinx.coroutines.launch
    import kotlinx.coroutines.runBlocking
    
    fun main() = runBlocking<Unit> {
        launch { r1() }
        println("DONE")
    }
    
    // 挂起函数
    suspend fun r1() {
        delay(300)
        println("[rustfisher] 提取出来的函数")
    }
    

    log

    DONE
    [rustfisher] 提取出来的函数
    

    协程是轻量的

    我们前面也试过,创建非常多的协程,程序运行OK。

    下面的代码可以输出很多的点

    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        for (t in 1..10000) {
            launch {
                delay(t * 500L)
                print(".")
            }
        }
    }
    

    全局协程像守护线程

    我们在线程介绍中知道,如果进程中只剩下了守护线程,那么虚拟机会退出。
    前文那个打印rustfisher.com的例子,其实也能看到,字符没打印完程序就结束了。

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

    再举一个例子

    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        GlobalScope.launch {
            for (i in 1..1000000) {
                delay(200)
                println("协程执行: $i")
            }
        }
    
        delay(1000)
        println("Bye~")
    }
    

    log

    协程执行: 1
    协程执行: 2
    协程执行: 3
    协程执行: 4
    Bye~
    

    最后我们来看一下全文的思路

    guide.png

    参考

    一个软件工程师的记录
  • 相关阅读:
    c 中有关打印*,字符的题目集
    c 求两个整数的最大公约数和最小公倍数
    cygwin中vim的使用方法
    window下如何搭建linux环境
    ORA-00923: 未找到要求的 FROM 关键字
    c# datagridviewcomboboxcell值无效的解决办法
    sql substring charindex
    C# 中datagridview行里面有三个cheeckbox,要控制成三选一。
    程序中使用事务来管理sql语句的执行,执行失败时,可以达到回滚的要求。
    WinForm界面中快捷键设置
  • 原文地址:https://www.cnblogs.com/rustfisher/p/15336721.html
Copyright © 2011-2022 走看看