Kotlin | 协程使用手册(不间断更新)(上)

简介: Kotlin协程作为Kotlin核心的一个组件,上手成本并不高,下面的demo都是我参照官网的例子过了一遍。

Kotlin协程作为Kotlin核心的一个组件,上手成本并不高,下面的demo都是我参照官网的例子过了一遍。

Kotlin中文网。

其中的Flow大家可以多花点时间,还是挺有意思的。

启动一个协程

fun main() {
    GlobalScope.launch {
        println(123)
    }
    Thread.sleep(10)
}

阻塞方式等待协程执行完再执行后续

    GlobalScope.launch {
        delay(1000)
        println(123)
    }.join()
    println(1231)

delay

特殊的挂起函数,它并不会造成函数阻塞,但是会挂起协程

协程作用域构建器

runBlocking

会阻塞当前线程,直到协程结束。 一般用于测试

 runBlocking {
                    launch(Dispatchers.IO) {
                        Log.e("demo", Thread.currentThread().name)
                        delay(10000)
                        println("!23")
                    }
                }

coroutineScope

只是挂起,会释放底层线程用于其他用途,并不会阻塞线程。所以我们称它为挂起函数

coroutineScope {
                    launch(Dispatchers.IO) {
                        Log.e("demo", Thread.currentThread().name)
                        delay(10000)
                        println("!23")
                    }
                }

结构化并发

虽然协程使用起来很简单,当我们使用 GlobalScope.launch 时,我们会创建一个顶级协程,但是这样使用也不是我们所推荐的方式,特别是如果我们忘记了对新启动协程的引用,它还是会继续运行。所以在实际应用中,我们更推荐 : 在执行操作所在指定作用域内启动协程,而非随意使用

协程的取消与超时

cancelAndJoin

取消一个协程并等待结束

   runBlocking {
        val startTime = System.currentTimeMillis()
        val job = launch(Dispatchers.Default) {
            var nextPrintTime = startTime
            var i = 0
            while (i < 5) { // 一个执行计算的循环,只是为了占用 CPU
                // 每秒打印消息两次
                if (System.currentTimeMillis() >= nextPrintTime) {
                    println("job: I'm sleeping ${i++} ...")
                    nextPrintTime += 500L
                }
            }
        }
        delay(1300L) // 等待一段时间
        println("main: I'm tired of waiting!")
        job.cancelAndJoin() // 取消一个作业并且等待它结束
        println("main: Now I can quit.")
    }

withTimeout

超时后抛出异常

//超时抛出异常
withTimeout(1300L) {
    delay(1400)
}

withTimeoutOrNull

超时后抛出null指针

//超时抛出异常
        withTimeoutOrNull(1300L){
            delay(1400)
        }

在finally中释放资源

coroutineScope {
    val a = launch {
        try {
            repeat(1000) { i ->
                println("日常打印")
                delay(500)
            }
        } finally {
            println("回收资源")
        }
    }
    delay(1000) //延迟一段时间
    println("延迟结束")
    a.cancelAndJoin()   //取消一个作业并等待它结束
}

在finally中重新挂起协程

在我们实际应用中,可能需要在finally重新挂起一个被取消的协程,所以可以将相应的代码包装在

**withContext(NoCancellable)**中

coroutineScope {
        val a = launch {
            try {
                repeat(1000) { i ->
                    println("日常打印")
                    delay(500)
                }
            } finally {
                withContext(NonCancellable){
                    println("挂起一个被取消的协程")
                    delay(1000)
                    println("挂起取消")
                }
            }
        }
        delay(1000)
        println("延迟结束")
        a.cancelAndJoin()   //取消一个作业并等待它结束
    }

超时抛出异常

设置超时时间,超过预期时间,抛出异常。可以用于倒计时等

coroutineScope {
    try {
        withTimeout(1000){
            println("超过2000ms就失败")
            delay(2000)
        }
    }catch (e:TimeoutCancellationException){
        println(e.message)
        println("好的好的,我知道了")
    }
}
超过2000ms就失败
Timed out waiting for 1000 ms
好的好的,我知道了

超时抛出null指针

有些情况,你可能并不想直接抛出异常,则可以让其抛出null指针

   coroutineScope {
        val time = withTimeoutOrNull(1000) {
            println("超过2000ms就失败")
            delay(2000)
        }
        println(time) //null
    }
超过2000ms就失败
null

组合挂起函数

默认顺序调用挂起函数

measureTimeMillis

suspend fun main() {
    val time = measureTimeMillis {
        playGame()
        playPP()
    }
    println("经过了$time ms")
}
suspend fun playGame() {
    delay(1000)
    println("打豆豆1秒")
}
suspend fun playPP() {
    delay(1000)
    println("打屁屁1秒")
}
打豆豆1秒
打屁屁1秒
经过了2031 ms

async

并发执行

在上面的例子中,我们按顺序执行,但我们实际开发中,更多的是希望并行执行,借助于 async  我们就可以实现。

注意

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

suspend fun main() {
    measureTimeMillis {
        coroutineScope {
            async { playGame() }
            async { playPP() }
        }
    }.let {
        println("所费时间+ $it")
    }
}
suspend fun playGame() {
    delay(1000)
    println("打豆豆1秒")
}
suspend fun playPP() {
    delay(1000)
    println("打屁屁1秒")
}
打豆豆1秒
打屁屁1秒
所费时间+ 1084

惰性启动的 async

我们可以通过更改 async 的属性来实现惰性模式,在这个模式下,只有通过 await 或者 async的返回值 job.start,才会启动

注意:如果直接调用await,那么结果将会是顺序执行

suspend fun main() {
    measureTimeMillis {
        coroutineScope {
            val game = async(start = LAZY) { playGame() }
            val pp = async(start = LAZY) { playPP() }
            game.start()
            pp.start()
            println("启动完成")
        }
    }.let(::println)
    }
suspend fun playGame() {
    delay(1000)
    println("打豆豆1秒")
}
suspend fun playPP() {
    delay(1000)
    println("打屁屁1秒")
}

async 风格的函数

我们可以定义异步风格的函数来异步调用 playGame 和 playPP,并使用 async 协程建造器并带有一个显式的 GlobalScope引用

suspend fun main() {
  measureTimeMillis {
        val somethingPlayGame = somethingPlayGame()
        val somethingPlayPP = somethingPlayPP()
        runBlocking {
            somethingPlayGame.await()
            somethingPlayPP.await()
        }
    }.let(::println)
}
fun somethingPlayGame() = GlobalScope.async {
    playGame()
}
fun somethingPlayPP() = GlobalScope.async {
    playPP()
}
suspend fun playGame() {
    delay(1000)
    println("打豆豆1秒")
}
suspend fun playPP() {
    delay(1000)
    println("打屁屁1秒")
}
打屁屁1秒
打豆豆1秒
1085

注意:这样的写法我们并不推荐。如果 val somethingPlayGame = somethingPlayGame() 和 somethingPlayGame.await() 有逻辑错误,程序将抛出异常,但是 somethingPlayPP 依然在后台执行,但是因为前者异常,所有协程都将被关闭,所以 somethingPlayPP 操作也会终止

案例如下

...
suspend fun playGame() {
    delay(500)
    throw ArithmeticException("error")
    println("打豆豆1秒")
}
...
Exception in thread "main" java.lang.ArithmeticException: error
  at com.xiecheng_demo.java.TestKt.playGame(Test.kt:46)
  ....

使用async正确的结构化并发

按照协程的层次结构传递

suspend fun main() {
    runBlocking {
        try {
            test()
        } catch (e: ArithmeticException) {
            println("main-${e.message}")
        }
    }
}
suspend fun test() = coroutineScope {
    async { playGame() }
    async { playPP() }
}
suspend fun playGame() {
    try {
        delay(1000)
        println("打豆豆1秒")
    } finally {
        println("我在打豆豆,万一异常了。。。")
    }
}
suspend fun playPP() {
    delay(500)
    throw ArithmeticException("抛出异常")
}
我在打豆豆,万一异常了。。。
main-抛出异常

注意:如果其中一个子协程失败,则第一个 playGame 和等待中的父协程都会被取消

协程上下文和调度器

协程总是运行在以 coroutineContext 为代表的上下文中,协程上下文是各种不同元素的集合,事实上, coroutineContext 就是一个存储协程信息的context

网络异常,图片无法展示
|

调度器

coroutineContext 包含了dispatchers,我们可以借助其限制协程的工作线程。

分别有如下几种:

  • Dispatchers.Default 协程默认线程
  • Dispatchers.IO io线程
  • Dispatchers.Main 主线程
  • Dispatchers.Unconfined 无限制,将直接运行在当前线程

子协程

当一个协程被其他协程在 CoroutineScope 启动时,它将通过  CoroutineScope.CoroutineContext来承袭上下文,并且这个新协程将成为父协程的子作业。当一个父协程被取消时,同时意味着所有的子协程也会取消。

然而,如果此时用 GlobalScope.launch启动子协程,则它与父协程的作用域将无关并且独立运行。

    val a = GlobalScope.launch {
        GlobalScope.launch {
            println("使用GlobalScope.launch启动")
            delay(1000)
            println("GlobalScope.launch-延迟结束")
        }
        launch {
            println("使用 launch 启动")
            delay(1000)
            println("launch-延迟结束")
        }
    }
    delay(500)
    println("取消父launch")
    a.cancel()
    delay(1000)
使用GlobalScope.launch启动
使用 launch 启动
取消父launch
GlobalScope.launch-延迟结束

join

使用 join 等待所有子协程执行完任务。

suspend fun main() {
    val a = GlobalScope.launch {
        GlobalScope.launch {
            println("使用GlobalScope.launch启动")
            delay(1000)
            println("GlobalScope.launch-延迟结束")
        }
        launch {
            println("使用 launch 启动")
            delay(1000)
            println("launch-延迟结束")
        }
    }
   a.join()
}

在上面main函数中,GlobalScope.launch启动的协程将立即独立执行,如果不使用join,则main可能瞬间执行完成,从而无法看不到效果。使用join方法从而使得 main 所在的协程暂停,直到 GlobalScope.launch 执行完成。

指定协程名

使用 + 操作符来指定

   GlobalScope.launch(Dispatchers.Default+CoroutineName("test")){
        println(Thread.currentThread().name)
    }.join()

这里使用了 jvm参数 -Dkotlinx.coroutines.debug


如何配置jvm参数:Android Studio,Intellij同理


网络异常,图片无法展示
|

协程作用域

在我们了解了上面的概念之后,我们开始将前面学到的结合在一起。定义一个全局的 协程。

class Main4Activity : AppCompatActivity() {
    private val mainScope = CoroutineScope(Dispatchers.Default)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main4)
        btn_start.setOnClickListener {
            mainScope.launch {
                //一波操作后
                launch(Dispatchers.Main) {
                    toast("one")
                }
            }
            Log.e("demo","123")
            mainScope.launch (Dispatchers.Main) {
                delay(3000)
                    toastCenter("two")
            }
        }
        btn_cancel.setOnClickListener {
            onDestrxx()
        }
    }
    fun onDestrxx() {
        mainScope.cancel()
    }
}

我们在Activity中声明了一个 CoroutineScope 对象,然后创建了一些作用域,这样当我们Activity destory的时候,就可以全部销毁。

这里为了节省代码,仿onDestory 的作用

效果,点击btn1之后,再点击btn2,将只会弹出一个toast,第二个toast将不会弹出

线程局部数据

将一些局部数据传递到协程之间通过 ThreadLoacl即可完成。

suspend fun main() {
    val threadLocal = ThreadLocal<String>()
    threadLocal.set("main")
    GlobalScope.launch(Dispatchers.Default+threadLocal.asContextElement(value = "123")) {
        println("thread-${Thread.currentThread().name},value=${threadLocal.get()}")
        println("thread-${Thread.currentThread().name},value=${threadLocal.get()}")
    }.join()
    println("thread-${Thread.currentThread().name},value=${threadLocal.get()}")
    delay(1000)
    println("thread-${Thread.currentThread().name},value=${threadLocal.get()}")
}
thread-DefaultDispatcher-worker-1 @coroutine#1,value=123
thread-DefaultDispatcher-worker-1 @coroutine#1,value=123
thread-main,value=main
thread-kotlinx.coroutines.DefaultExecutor,value=null

你可能会疑问**,为什么delay 之后,threadloadl.get为null**?

请注意main函数前面加了一个 suspend,而main函数内部就相当于协程体,当我们直接调用 GlobalScope.launch 时,它直接独立运行,此时内部的 coroutineContext 为我们手动传递的。

而当我们调用了 delay之后,直接挂起协程,此时我们的main函数中的 coroutineContext 即为默认值null,于是get为null

目录
相关文章
|
17天前
|
存储 Java 调度
Android面试题之Kotlin 协程的挂起、执行和恢复过程
了解Kotlin协程的挂起、执行和恢复机制。挂起时,状态和上下文(局部变量、调用栈、调度器等)被保存;挂起点通过`Continuation`对象处理,释放线程控制权。当恢复条件满足,调度器重新分配线程,调用`resumeWith`恢复执行。关注公众号“AntDream”获取更多并发知识。
28 2
|
2月前
|
移动开发 Android开发 开发者
构建高效Android应用:Kotlin与协程的完美融合
【5月更文挑战第25天】 在移动开发的世界中,性能和响应性是衡量应用质量的关键指标。随着Kotlin的流行和协程的引入,Android开发者现在有了更强大的工具来提升应用的性能和用户体验。本文深入探讨了Kotlin语言如何与协程相结合,为Android应用开发带来异步处理能力的同时,保持代码的简洁性和可读性。我们将通过实际案例分析,展示如何在Android项目中实现协程,以及它们如何帮助开发者更有效地管理后台任务和用户界面的流畅交互。
|
2月前
|
移动开发 数据库 Android开发
构建高效Android应用:探究Kotlin的协程优势
【5月更文挑战第22天】随着移动开发技术的不断进步,Android平台的性能优化已经成为开发者关注的焦点。在众多提升应用性能的手段中,Kotlin语言提供的协程概念因其轻量级线程管理和异步编程能力而受到广泛关注。本文将深入探讨Kotlin协程在Android开发中的应用,以及它如何帮助开发者构建出更高效、响应更快的应用,同时保持代码的简洁性和可读性。
|
2月前
|
移动开发 监控 Android开发
构建高效安卓应用:Kotlin 协程的实践与优化
【5月更文挑战第16天】 在移动开发领域,性能优化一直是开发者们追求的重要目标。特别是对于安卓平台来说,由于设备多样性和系统资源的限制,如何提升应用的响应性和流畅度成为了一个关键议题。近年来,Kotlin 语言因其简洁、安全和高效的特点,在安卓开发中得到了广泛的应用。其中,Kotlin 协程作为一种轻量级的并发解决方案,为异步编程提供了强大支持,成为提升安卓应用性能的有效手段。本文将深入探讨 Kotlin 协程在安卓开发中的应用实践,以及通过合理设计和使用协程来优化应用性能的策略。
33 8
|
2月前
|
移动开发 Android开发 开发者
构建高效安卓应用:Kotlin 协程的实践指南
【5月更文挑战第18天】 随着移动开发技术的不断进步,安卓平台亟需一种高效的异步编程解决方案来应对日益复杂的应用需求。Kotlin 协程作为一种新兴的轻量级线程管理机制,以其简洁的语法和强大的功能,成为解决这一问题的关键。本文将深入探讨Kotlin协程在安卓开发中的实际应用,从基本概念到高级技巧,为开发者提供一份全面的实践指南,旨在帮助读者构建更加高效、稳定的安卓应用。
|
2月前
|
移动开发 安全 Android开发
构建高效Android应用:Kotlin与协程的完美结合
【5月更文挑战第17天】 在移动开发领域,性能优化和流畅的用户体验是关键。对于Android平台而言,Kotlin语言凭借其简洁性和功能安全性成为开发的首选。与此同时,协程作为一种新的并发处理方式,在简化异步编程方面展现出巨大潜力。本文将深入探讨如何通过Kotlin语言以及协程技术,提升Android应用的性能和响应能力,并确保用户界面的流畅性。
|
2月前
|
移动开发 数据处理 Android开发
构建高效Android应用:Kotlin的协程与Flow的使用
【5月更文挑战第23天】 在移动开发领域,性能优化和异步编程一直是核心议题。随着Kotlin语言在Android开发中的普及,其提供的协程(coroutines)和流式编程(Flow)功能为开发者带来了革命性的工具,以更简洁、高效的方式处理异步任务和数据流。本文将深入探讨Kotlin协程和Flow在Android应用中的实际应用,以及它们如何帮助开发者编写更加响应迅速且不阻塞用户界面的应用程序。我们将通过具体案例分析这两种技术的优势,并展示如何在现有项目中实现这些功能。
|
2月前
|
安全 调度 Python
探索Python中的并发编程:协程与多线程的比较
本文将深入探讨Python中的并发编程技术,重点比较协程与多线程的特点和应用场景。通过对协程和多线程的原理解析,以及在实际项目中的应用案例分析,读者将能够更好地理解两种并发编程模型的异同,并在实践中选择合适的方案来提升Python程序的性能和效率。
|
7天前
|
数据挖掘 程序员 调度
Python并发编程之协程与异步IO
传统的多线程和多进程模型在处理大规模并发时存在一些性能瓶颈和资源消耗问题。本文将重点介绍Python中基于协程和异步IO的并发编程方法,探讨其工作原理和实际应用,帮助开发者更好地理解并利用Python的并发编程能力。
|
8天前
|
开发者 Python
探索 Python 中的协程:从基本概念到实际应用
在现代编程中,异步处理变得越来越重要,Python 通过其内置的协程提供了强大的工具来简化这一过程。本文将深入探讨 Python 中的协程,从基本概念出发,逐步展示其实际应用,并通过具体代码示例帮助你掌握这种技术。