Kotlin 中的 协程 基础篇

一、什么叫协程

协程可以称为轻量级线程,线程代码块;

二、GlobalScope

协程 CoroutineScope (协程作用域) 的上下文中通过 launch、async 等构造器来启动。GlobalScope ,即全局作用域内启动了一个新的协程,这意味这该协程的生命周期只受整个应用程序的生命周期的限制,即只要整个应用程序还在运行中,只要协程的任务还未结束,该协程就可以一直运行。

Kotlin 复制代码
    //在后台启动一个新协程
    GlobalScope.launch {
        delay(3000)//非阻塞式延迟
        println("World!")
    }
    println("Hello ")
    Thread.sleep(4000)//主线程阻塞 4秒,以此保证JVM 存活

输出:

Hello

World!

三、runBlocking

非阻塞代码 delay() 和 阻塞代码 Thread.sleep() ,使得我们很容易就搞混当前程序是否是阻塞的,可以改为 runBlocking 来明确这种情形。

Kotlin 复制代码
fun test2(){
    GlobalScope.launch {
        delay(1000)
        println("World!")
    }
    println("Hello ")
    runBlocking {
        delay(2000)
    }
}

运行结果和第一个程序一样,但是这段代码只使用了非阻塞延迟。主线程调用了 runBlocking 函数,直到 runBlocking 内部的所有协程执行完成后,之后的代码才会继续执行。

可以将以上代码用更喜欢方式重写,使用 runBlicking 来包装 main 函数的执行体:

Kotlin 复制代码
fun test3() {
    runBlocking<Unit> {
        GlobalScope.launch {
            delay(1000)
            println("World!")
        }
        println("Hello ")
        delay(2000)
    }
}

需要注意是,runBlocking 代码块默认运行于其声明所在的线程,而 launch 代码块默认运行于线程池中,可以通过打印当前线程名来进行区分。

Kotlin 复制代码
fun test3() {
    runBlocking<Unit> {
        println(Thread.currentThread().name)
        GlobalScope.launch {
            println(Thread.currentThread().name)
            delay(1000)
            println("World!")
        }
        println("Hello ")
        delay(2000)
    }
}

输出:

main // runblocking 运行 main 线程

Hello

DefaultDispatcher-worker-1 //GlobalScope 运行线程池

World!

四、job

延迟一段时间等待另一个协程运行并不是一个好的选择,可以显式(非阻塞的方式)地等待协程执行完成

Kotlin 复制代码
fun test4() {
    runBlocking {
        val job = GlobalScope.launch {
            delay(1000)
            print("World!")
        }
        println("Hello ")
        job.join()
    }
}

现在代码的运行结果仍然是相同,但是主协程与后台作业的持续时间没有任何关系,这样好多了。

五、launch

Launch 函数是 CoroutineScope 的扩展函数,而 runBlocking 的函数体参数也是被声明为 CoroutineScope 的扩展函数,所以 launch 函数就隐式持有了和 runBlicking 相同的协程作用域。此时即使 delay 再久, println("World!") 也一定会被执行。

Kotlin 复制代码
fun test5() {
    runBlocking {
        launch {
            delay(1000)
            println("World!")
        }
        println("Hello ")
    }
}

六、CoroutineScope

除了使用官方的几个协程构建器之外,还可以使用 coroutineScope 来声明自己的作用域。 coroutineScope 用于创建一个协程作用域,直到所有启动的子协程都完成后才结束。

RunBlocking 和 coroutineScope 看起来很像,因为它们都是需要等待其它内部所有相同作用域的子协程结束后才会结束自己。两者的主要区别在于 runBlocking 方法会阻塞当前线程,而 coroutineScope 只是挂起并释放底层底层线程以供其他协程使用。由于这个差别,所以 runBlocking 是一个普通函数,而 coroutineScope 是一个挂起函数。

Kotlin 复制代码
fun test6(){
    runBlocking {
        //非阻塞 GlobalScope
        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")
    }
}

输出:

Task from coroutine scope

Task from runBlocking

Task from nested launch

Coroutine scope is over

六、suspend

suspend 挂起函数可以想常规函数一样在协程中使用,但是它们的额外特性是:可以依次使用其他挂起函数(如delay函数)来使协程挂起。

Kotlin 复制代码
fun test7() {
    runBlocking {
        //内部调用挂起函数,执行耗时任务
        launch {
            doWorld()
        }
        println("Hello ")
    }
}

suspend fun doWorld() {
    delay(1000)
    println("World!")
}

七、repeat

协程是轻量级的

Kotlin 复制代码
fun test8() {
    runBlocking {
        //启动 10万个协程
        repeat(100_000) { i ->
            launch {
                println("I'm sleeping $i ...")
                delay(500)
            }
        }
    }
}

八、全局协程类似于守护线程

以下代码在 GlobalScope 中启动了一个会长时间运行的协程,它每秒打印两次,然后延迟一段时间后从 mian 函数返回。

Kotlin 复制代码
fun test9() {
    runBlocking {
        GlobalScope.launch {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500)
            }
        }
        delay(1300)
    }
}

输出:

I'm sleeping 0 ...

I'm sleeping 1 ...

I'm sleeping 2 ...

这里由于 launch 函数依附的协程作用域是 GlobalScope,而非 runBlocking 所隐含的作用域。在 GlobalScope 中启动的协程无法使进程保持活动状态,它们像守护线程(当主线程消亡时,守护线程也将消亡)

八、协程代码块默认顺序执行

Kotlin 复制代码
fun test10() {
    runBlocking {
        val time = measureTimeMillis {
            val one = doSomethingUsefulOne()
            val two = doSomethingUsefulTwo()
            println("The answer is ${one + two}")
        }
        println("Completed in $time ms")
    }
}

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

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000)
    return 29
}

输出:

The answer is 42

Completed in 2011 ms

九、async

async 实现多个协程异步执行,并可以通过 await() 取出结果返回值,launch 类似 async,但是无返回值。

Kotlin 复制代码
fun test11(){
    runBlocking {
        val time = measureTimeMillis {
            val one = async { doSomethingUsefulOne() }
            val two = async { doSomethingUsefulTwo() }
            println("The answer is ${one.await() + two.await()}")
        }
        println("Completed in $time ms")
    }
}

输出:

The answer is 42

Completed in 1019 ms

十、lazy

async 可以设置 CoroutineStart.lazy 为懒加载模式。在这情况下 需要 调用 await() 或者 start() 才启动。

Kotlin 复制代码
fun test12(){
    runBlocking {
        val time = measureTimeMillis {
            val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
            val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }

            one.start()
            two.start()
            println("The answer is ${one.await() + two.await()}")
        }
        println("Completed in $time ms")
    }
}
相关推荐
程序喵大人1 分钟前
【C++进阶】STL容器与迭代器 - 10 把容器、迭代器和数据流串成一个小程序
开发语言·c++·容器·小程序·迭代器·stl
luj_176822 分钟前
随机性在算法与占卜中的共通原理
c语言·开发语言·c++·经验分享·算法
Lazionr29 分钟前
vector初识——从动态数组到STL核心容器
开发语言·c++
AI视觉网奇31 分钟前
glb读取颜色
开发语言·python
程序员-Benothing2 小时前
如何实现高并发系统订单30分钟自动关闭?
开发语言·c#·wpf
郝学胜-神的一滴3 小时前
Python 高级编程 026:内置数据结构之骈文纵论
开发语言·数据结构·python·程序人生·软件工程
隐积10 小时前
3.1.7.Qt容器大家族(3):QVariant——万能盒子
开发语言·qt
名字还没想好☜13 小时前
Next.js ‘use client‘ 到底加在哪:Server/Client Components 边界与常见报错
开发语言·前端·javascript·react·next.js
初级代码游戏14 小时前
iOS开发 Swift 速记1:变量和基本数据类型
开发语言·ios·swift
红尘伴蝶舞15 小时前
Android 17 局域网调试与 ACCESS_LOCAL_NETWORK 权限隔离实践
kotlin·测试·设计