Kotlin-浅谈协程

目录
1.协程是什么
2.协程的作用
3.协程的架构
4.协程的原理
5.协程的创建
6.协程的调度
7.协程的取消&超时处理
8.与Go和Java相比较

协程是什么

  1. 协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器)上调度执行,而代码则保持如同顺序执行一样简单。

  2. 协程是一种并发设计模式,您可以在Android平台上使用它来简化异步执行的代码

简单的来说:我们可以以同步的方式去编写异步执行的代码

协程的作用

1.协程可以让异步代码同步化
2.协程可以降低异步程序的设计复杂度

协程有哪些优点

  1. 协程是我们在 Android 上进行异步编程的推荐解决方案。值得关注的特点包括:
  2. 轻量:您可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
  3. 内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
  4. 内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。
  5. Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发。

协程的架构

kotlin的协程实现分为了两个层次:
Kotlin-浅谈协程_第1张图片

  1. 基础设施层:标准库的协程API,主要对协程提供了概念和语义上最基本的支持;

  2. 业务框架层 kotlin.coroutines:协程的上层框架支持,基于标准库实现的封装,也是我们日常开发使用的协程扩展库。

协程的原理

协程的概念最核心的点就是函数或者程序块能够被挂起,并且能够再在挂起的位置恢复。协程通过主动让出运行权来实现协作,程序自己处理挂起和恢复来实现程序执行流程的协作调度。因此它本质上就是在规范程序控制流程的机制。

协程使用同步的方式完成异步任务,而且代码简洁,这也是Kotlin协程的魅力所在。之所有可以用看起来同步的方式写异步代码,关键在于请求函数getUserSuspend()是一个挂起函数,被suspend关键字修饰

Kotlin-浅谈协程_第2张图片

从上面的协程原理图解中可以看出,耗时阻塞的操作并没有减少,只是交给了另一个IO线程。userApi.getUserSuspend(“suming”)真正执行的时候会切换到IO线程中执行,获取结果后最后恢复到主线程上,然后继续执行剩下的流程。

将业务流程原理拆分得更细致一点,在主线程中创建协程A中执行整个业务流程,如果遇到异步调用任务则协程A被挂起,切换到IO线程中创建子协程B,获取结果后再恢复到主线程的协程A上,然后继续执行剩下的流程。

协程虽然不能脱离线程而运行,但可以在不同的线程之间切换,而且一个线程上可以运行一个或多个协程。

Kotlin-浅谈协程_第3张图片

协程在Android上使用场景

kotlin协程基于Thread相关API的封装,让我们不用过多关心线程也可以方便地写出并发操作,这就是Kotlin的协程。协程的好处本质上和其他线程api一样,使用方便。

在 Android 平台上,协程有两个主要使用场景:
1.线程切换,保证线程安全。
2.处理耗时任务(比如网络请求、解析JSON数据、从数据库中进行读写操作等)。

协程怎么添加

在 Android 项目中使用协程,请将以下依赖项添加到应用的 build.gradle 文件中:

dependencies {
     //协程标准库
    implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.32"
    //协程核心库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
    //协程Android支持库,提供安卓UI调度器
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
}

协程的创建

创建协程的三种方式:

  1. 使用 runBlocking 顶层函数创建

  2. 使用 GlobalScope 单例对象创建

  3. 自行通过 CoroutineContext 创建一个 CoroutineScope 对象

举例

        runBlocking {
            Log.i(TAG, "启动协程runBlocking.")
        }

        GlobalScope.launch {
            Log.i(TAG, "启动协程launch.")
        }

        val coroutineScope = CoroutineScope(context)
        coroutineScope.launch {
           Log.i(TAG, "启动协程launch.")
        }

比较

方法一:通常适用于单元测试的场景,而业务开发中不会用到这种方法,因为它是线程阻塞的。

方法二:和使用 runBlocking 的区别在于不会阻塞线程。但在 Android 开发中同样不推荐这种用法,因为它的生命周期会只受整个应用程序的生命周期限制,且不能取消。

方法三:是比较推荐的使用方法,我们可以通过 context 参数去管理和控制协程的生命周期(这里的 context 和 Android 里的不是一个东西,是一个更通用的概念,会有一个 Android 平台的封装来配合使用)。

协程的调度器

Kotlin 提供了四个调度器:

Dispatchers.Default
Dispatchers.Main
Dispatchers.Unconfined
Dispatchers.IO

注意:当启动协程时如果不带参数的情况下,它从外部的协程作用域继承上下文和调度器

Kotlin-浅谈协程_第4张图片

举例

 GlobalScope.launch(Dispatchers.Main) {
            Log.i(TAG, "主线程调度器.")
  }
  GlobalScope.launch(Dispatchers.Default) {
            Log.i(TAG, "默认调度器.")
  }
  GlobalScope.launch(Dispatchers.Unconfined) {
            Log.i(TAG, "任意调度器.")
   }
   GlobalScope.launch(Dispatchers.IO) {
            Log.i(TAG, "IO调度器.")
   }

协程的取消和超时

每一个协程创建时,都会生成一个 Job 实例,这个实例是协程的唯一标识,负责管理协程的生命周期。当协程创建、执行、完成、取消时,Job 的状态也会随之改变,通过检查 Job 对象的属性可以追踪到协程当前的状态

Job 一共包含六个状态:
新创建 New
活跃 Active
完成中 Completing
已完成 Completed
取消中 Cancelling
已取消 Cancelled

Job生命周期
通常来说,Job 的生命周期会经过四个状态:New ——> Active——> Completing ——>Completed。

协程在 Active 和 Completing 时可以响应取消命令。如果收到了取消命令,协程会马上经过 Cancelling ——>Cancelled 生命周期。
Kotlin-浅谈协程_第5张图片
Job超时处理
使用 withTimeout 或 withTimeoutOrNull 可以指定 Job 任务的超时时间。两者的区别在于:

withTimeout 函数会在超时后抛出一个超时异常 TimeoutCancellationException
withTimeoutOrNull 函数会在超时后返回一个 null 值

举例

  runBlocking {
            try {
                withTimeout(500) {
                    launch {
                        delay(1000)
                    }
                }
            } catch (e: TimeoutCancellationException) {
                e.printStackTrace()
            }
        }
输出:2023-03-21 20:41:27.078 28730-28730/com.example.kotlindemo W/System.err: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 500 ms
        runBlocking {
            val result = withTimeoutOrNull(500) {
                launch {
                    delay(1000)
                }
            }
            Log.i(TAG, "withTimeoutOrNull: " + result)
        }


输出:2023-03-21 20:43:35.887 28893-28893/com.example.kotlindemo I/Coroutine: withTimeoutOrNull: null

与Go和Java相比较

Go 中的协程
Go 协程(Goroutine)是与其他函数同时运行的函数。可以认为 Go 协程是轻量级的线程,由 Go 运行时来管理。

在函数调用前加上 go 关键字,这次调用就会在一个新的 goroutine 中并发执行。当被调用的函数返回时,这个 goroutine 也自动结束。听起来像 C# 中的 Task。

需要注意的是,如果这个函数有返回值,那么这个返回值会被丢弃。

Go 协程(Goroutine)之间通过信道(channel)进行通信,简单的说就是多个协程之间通信的管道。信道可以防止多个协程访问共享内存时发生资源争抢的问题。

举例

package main

import ( "fmt"
    "time" )

func hello() {  
    fmt.Println("Hello world goroutine")
}
func main() {  
    go hello()
    time.Sleep(1 * time.Second)
    fmt.Println("main function")
}

Java
Java 原生语言暂时不支持协程的,但是在使用Java进行Android项目开发时可以使用以下方法实现类似协程的作用。

  1. Handler
  2. AsyncTask
  3. Rxjava
    4.线程/线程池

缺点:

  1. 如果处理不当,容易造成内存泄漏
  2. 代码结构混乱,不利于整体架构规范
  3. 编码较复杂,上手难

你可能感兴趣的:(kotlin,android)