Kotlin 协程 — 基础

Kotlin 协程 — 基础

协程已经存在一段时间了,关于它的各种文章也很多。但我发现想要了解它还比较费时,所以我花了一段时间才真正理解了协程的基础知识以及它的工作原理。因此,我想分享一些我理解到的内容。

什么是协程?

协程代表合作函数。它们提供了一种更有效和易读的方式来处理异步任务。它与线程类似,因为它需要一块代码来与其余代码同时运行。然而,协程并不绑定到任何特定线程。它可以在一个线程中暂停执行,并在另一个线程中恢复。协程在 Kotlin 1.3 版本中推出。

协程的优势

  • 轻量级 — 我们可以在单个线程上运行许多协程,这归功于其对挂起的支持。挂起意味着你可以执行一些指令,然后在执行中间停止协程,并在需要时继续。挂起节省了内存,同时支持许多并发操作,而不是阻塞。

  • 减少内存泄漏 — 协程遵循结构化并发原则,这意味着每个协程应该在具有确定生命周期的特定上下文中启动。结构化并发是一种方法,其中协程的生命周期与特定作用域绑定,确保在作用域本身完成之前,该作用域内启动的所有协程都已完成。这有助于避免协程泄漏并简化资源管理。

  • 在 Android 上提供主线程安全性 — 协程帮助管理可能阻塞主线程的长时间运行任务,从而使应用程序变得无响应。主线程安全性允许你确保任何挂起函数都可以从主线程调用。

  • 内置取消支持 — 协程最重要的机制之一是取消,因为在 Android 上,几乎每个协程都与某个视图关联,如果这个视图被销毁了,它的协程就不需要了,所以应该被取消。这是以前需要开发者付出很多努力的关键功能,但协程提供了一个简单和安全的取消机制。

  • 协作式多任务处理 — 这意味着协程是一组通过协作执行一系列指令的并发原语,操作系统不控制由协程执行的任务或进程的调度。相反,它依赖于运行它们的程序和平台来执行该操作。因此,协程可以交回控制权给调度器,以允许其他线程运行。操作系统的调度器负责让这些线程执行它们的工作,如果需要,也可以暂停它们,以便其他线程可以使用相同的资源。

协程的关键词

这些是你在学习协程时会遇到的一些常见关键词。

dbc9047e32b664e4ddcb5abd20b26113.png

  • suspend functions

  • Coroutine Scope (includes Dispatchers, Job)

  • Coroutine builders

  • Coroutine Context

suspend 函数

挂起函数是指那些可以暂停并在以后继续的函数。挂起函数表明该函数可以被挂起,允许其他协程在它等待非阻塞操作完成时运行。当挂起函数执行时,协程释放了它正在运行的线程,并允许其他协程访问该线程(因为协程是协作式的)。

挂起函数的语法与普通函数相同,只是加上了 suspend 关键字。挂起函数只允许从协程或其他挂起函数中调用。

suspend fun doSomething(): Int {delay(1000L) // 假设我们在这里做一些有用的事return 13
}

协程作用域

协程作用域定义了协程的生命周期/时长。它负责控制一组协程及其上下文的生命周期。一个 CoroutineScope 跟踪它创建的所有协程。因此,如果你取消了一个作用域,你就取消了它创建的所有协程。当子协程在父协程内启动时,它继承了父作用域(除非另有说明),以便当父协程停止时,子协程也会停止。

在 Android 中,协程有三个基本作用域:

  • 全局作用域 (GlobalScope): 全局作用域是一个预定义的协程作用域,持续整个应用程序的生命周期。虽然它可能很方便,但通常建议使用自定义协程作用域以确保结构化并发。

    GlobalScope.launch {val config = fetchConfigFromServer() // 网络请求updateConfiguration(config)
    }
  • 生命周期作用域 (LifecycleScope): 绑定到 LifecycleOwner(如 Fragment 或 Activity)的生命周期。当 Fragment 或 Activity 被销毁时,这个作用域中的协程也会被取消。使用 LifecycleScope 我们还可以利用特殊的启动条件:

    lifecycleScope.launchWhenResumed {println("loading..")delay(3000)println("job is done")
    }
    • launchWhenCreated 会在生命周期至少处于创建状态时启动协程,并在销毁状态时挂起。

    • launchWhenStarted 会在生命周期至少处于开始状态时启动协程,并在停止状态时挂起。

    • launchWhenResumed 会在生命周期至少处于恢复状态时启动协程,并在暂停状态时挂起。

  • ViewModel 作用域 (ViewModelScope): 绑定到 ViewModel 的生命周期。当 ViewModel 清除时,这个作用域中的协程也会被取消。

    viewModelScope.launch {println("loading..")delay(3000)println("job is done")
    }

协程构建器

协程构建器是用于初始化或创建新协程的函数。它们提供了一种方便的方式来启动和控制协程的执行。

  • launch: 启动一个新的协程并发执行,即不阻塞当前线程。它自动在生成的工作被取消时被取消,并且它不返回任何结果。launch 的返回类型是 Job。这意味着你可以通过与该工作交互来控制协程的生命周期。你可以通过调用 job.cancel() 轻松地取消它。在 ViewModel 中经常使用 launch 来从非挂起代码创建一个桥接到挂起代码。

    launch {delay(1000L)println("Hello World!")    
    }
  • runBlocking: 运行一个新的协程并阻塞当前线程直到其完成。换句话说,运行它的线程在 runBlocking 的括号内的所有代码块完成执行之前被阻塞。

    fun main() = runBlocking { // this: CoroutineScopedoWorld()
    }suspend fun doWorld() {delay(1000L)println("Hello Kotlin!")
    }
  • async: 像 launch 函数一样,它也用于启动一个新的协程;唯一的区别是它返回一个 Deferred 而不是 JobDeferred 是一个非阻塞的 future,承诺稍后提供结果。当生成的 Deferred 被取消时,运行的协程也会被取消。async 构建器允许你通过调用 await 来获取返回的值。

    fun main() = runBlocking<Unit> {val time = measureTimeMillis {val one = async { doSomethingUsefulOne() }val two = async { doSomethingUsefulTwo() }println("The answer is ${one.await() + two.await()}")}println("Completed in $time ms")}

协程上下文

协程上下文是一组定义协程行为和特性的元素。它包括调度器、工作、异常处理器和协程名称等。上下文用于确定协程将如何以及在何处执行。

调度器

协程调度器负责确定协程将被执行的线程或线程。调度器有 4 种类型:

  • 主线程调度器 (Main Dispatchers): 在主线程上执行协程。主线程调度器大部分在 UI 上工作。

  • I/O 调度器 (IO Dispatchers): 在 I/O 线程上启动协程。此调度器使用按需创建的共享线程池。这适合可能阻塞执行线程的 I/O 操作,例如读取或写入文件、执行数据库查询或进行网络请求。

  • 默认调度器 (Default Dispatchers): 用于当作用域中没有明确指定其他调度器时。它利用共享后台线程池。这是计算密集型协程需要 CPU 资源的好选择。

  • 不受限调度器 (Unconfined Dispatcher): 允许协程在任何线程上运行,甚至在每次恢复时使用不同的线程。它适用于不消耗 CPU 或更新特定线程的共享数据的协程。

fun main() = runBlocking {launch { // 父 runBlocking 协程的上下文println("main runBlocking      : I'm working in thread ${Thread.currentThread().name}")}launch(Dispatchers.Unconfined) { // 不受限 -- 将使用主线程println("Unconfined            : I'm working in thread ${Thread.currentThread().name}")}launch(Dispatchers.Default) { // 将被派发到 DefaultDispatcherprintln("Default               : I'm working in thread ${Thread.currentThread().name}")}launch(newSingleThreadContext("MyOwnThread")) { // 将获得自己的新线程println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")}    
}

你也可以通过使用 Executor.asCoroutineDispatcher() 扩展函数将它们转换为 CoroutineDispatcher 来在任何你自己的线程池中执行协程。可以使用以下方法创建私有线程池:

  • newSingleThreadContext(): 使用内置让步支持的专用线程构建协程执行环境。它是一个精细的 API,分配了本地资源(线程本身),需要仔细管理。

  • newFixedThreadPoolContext(): 建立一个固定大小的线程池的协程执行环境,可以在仔细管理线程资源的同时并行执行协程。

协程 Job

每次创建协程时,都会返回一个 Job 实例,以唯一标识该协程并允许你管理其生命周期。工作作为队列中的协程的句柄。一个工作有一组定义的状态:新建、活跃、完成中、已完成、取消中和已取消。我们不能直接访问这些状态本身,但我们可以访问工作的属性:isActiveisCancelledisCompleted

val job = launch { // 启动一个新协程并保留对其工作的引用delay(1000L)println("Hello World!")
}
job.join() // 等待子协程完成
println("Done")

SupervisorJob

它是 Job 的一个实现,作为子协程的监管者。它与常规工作类似,唯一的例外是它的子项可以相互独立地失败。子项的失败或取消不会导致监管者的工作失败或影响其其他子项,因此监管者可以为其子项的失败创建一个独特的处理策略。

fun main() = runBlocking {val supervisorJob = SupervisorJob()val coroutine1 = launch(supervisorJob) {println("Coroutine 1")throw RuntimeException("Error in Coroutine 1")}val coroutine2 = launch(supervisorJob) {println("Coroutine 2")delay(500)println("Coroutine 2 completed")}coroutine1.join()coroutine2.join()println("Parent coroutine: ${supervisorJob.isActive}") // 输出: Parent coroutine: true
}

协程取消

协程的取消由 Job 管理(Job 是我们处理协程的句柄,它具有生命周期)。我们可以通过在其 Job 上调用 .cancel() 函数来取消协程。当启动多个协程时,我们可以依赖于取消协程被启动到的整个作用域,因为这将取消所有创建的子协程。

取消只不过是抛出一个 CancellationException。这里的关键区别在于,如果协程抛出一个 CancellationException,它被认为是正常取消的,而任何其他异常都被认为是失败。虽然来自协程库的挂起函数可以安全地取消,但在编写自己的代码时,应始终考虑与取消合作。

使代码可取消的一种方法是明确检查当前工作的状态。我们可以使用 isActive() 扩展函数在 CoroutineContextCoroutineScope 上进行操作。检查取消的另一种常见方式是调用 ensureActive(),这是 JobCoroutineContextCoroutineScope 上可用的扩展函数。有关取消的更多细节可以在这里和这里找到。

感谢阅读!如果你学到了新东西,请关注我获取更多

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1486731.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

关于OLED的I2C手册记录

首先我们从淘宝上面找到对应OLED 4pin iic驱动的ssd1306手册&#xff0c;它有多种的驱动方式&#xff0c;我们只需要看看他这个i2c模式。 我们可以从中看到 Slave address R/W后面的#代表低电平是W。 SA0是它的一个 slave address bit 可以使用 这两个都可以作为OLED的 设备…

分布式事务与Seata落地

分布式事务与Seata落地 一、事务基础 1.1 本地事务 事务指的就是一个操作单元, 在这个操作单元中的所有操作最终要保持一致的行为, 要么所有操作都成功, 要么所有的操作都被撤销。 1.2 本地事务特性 本地事务四大特性: ACID A: 原子性(Atomicity), 一个事务中的所有操作, …

如何通过一条SQL变更多个分库分表?

数据库发展到今天&#xff0c;分库分表已经不是什么新鲜话题了&#xff0c;传统的单节点数据库架构在数据量和访问频次达到一定规模时&#xff0c;会出现性能瓶颈和扩展性问题&#xff0c;而分库分表技术通过将数据分散到多个数据库实例中来分担负载&#xff0c;从而提升系统的…

数字信号||离散序列的基本运算(2)

实验二 离散序列的基本运算 一、实验目的 (1)进一步了解离散时间序列时域的基本运算。 (2)了解MATLAB语言进行离散序列运算的常用函数&#xff0c;掌握离散序列运算程序的编写方法。 二、实验涉及的MATLAB子函数 1.find 功能&#xff1a;寻找非零元素的索引号。 调用格…

BGP选路之Local Preference

原理概述 当一台BGP路由器中存在多条去往同一目标网络的BGP路由时&#xff0c;BGP协议会对这些BGP路由的属性进行比较&#xff0c;以确定去往该目标网络的最优BGP路由。BGP首先比较的是路由信息的首选值&#xff08;PrefVal)&#xff0c;如果 PrefVal相同&#xff0c;就会比较本…

Linux_权限3

Linux所对应的文件类型 1.在Win下&#xff0c;有文件类型&#xff0c;通常通过后缀标识 日常用的就是windows系统这里不做举例. 2.Linux的文件类型不通过后缀区分&#xff08;不代表Linux不用后缀) 其中需要注意的是第一个字符表示文件类型的含义 - :普通文件, 文本, 源代码…

解决:uniapp 小程序 使用swiper 内部嵌套另外一个拥有左右滑动组件导致滑动冲突

解决办法 在swiper-item 内增加这个属性进行包裹 touchmove.stop <div touchmove.stop><qiun-data-charts type"area" :opts"optsStg" :chartData"dateDataStg" /> </div>

嘉立创|如何在原理图中框选任意元件

点击编辑—选择对象—对边形内部 便可以任意框选 选中之后&#xff0c;进入pcb板界面也选中了相同器件

使用kali对操作系统和网络服务类型进行探测

1&#xff0e;在Kali终端中输入命令“nmap –sS –n -O 192.168.2.2”&#xff0c;探测目标主机的操作系统类型 2&#xff0e; 在Kali终端中输入命令“nmap –sV -n 192.168.2.2”&#xff0c;探测目标主机开启的网络服务类型 3.在Kali终端中输入命令“nmap –A -n 192.168.2.2…

Linux中的System V通信标准--共享内存、消息队列以及信号量

关于 System V 标准&#xff0c;一共有三种通信方式&#xff0c;分别为&#xff1a;共享内存、信号量和消息队列三种通信方式。本篇将较为详细的讲解三种通信方式的实现原理&#xff0c;以及介绍在 Linux 系统下调用这三种的通信方式的接口&#xff0c;其中以共享内存为例&…

.netcore TSC打印机打印

此文章给出两种打印案例&#xff0c; 第一种是单列打印&#xff0c;第二种是双列打印 需要注意打印机名称的设置&#xff0c;程序中使用的打印机名称为999&#xff0c;电脑中安装打印机时名称也要为999。 以下是我在使用过程中总结的一些问题&#xff1a; 一 TSC打印机使用使…

【区块链+绿色低碳】巴中市生态价值核算创新应用 | FISCO BCOS应用案例

生态产品总值&#xff08;GEP&#xff09;&#xff0c;指一定区域生态系统为人类福祉和经济社会可持续发展提供的产品与服务价值总和&#xff0c;包 括供给产品价值、调节服务价值和文化服务价值。当前&#xff0c;推动生态产品价值有效转化存在“难度量、难抵押、难交易、 难变…

【机器学习算法基础】(基础机器学习课程)-08-决策树和随机森林-笔记

一、决策树之信息论基础 决策树是一种用来做决策的工具&#xff0c;就像我们生活中的选择树。例如&#xff0c;你在选择今天穿什么衣服时&#xff0c;会根据天气情况、出行活动等进行判断。决策树的构建过程涉及一些信息论的概念&#xff0c;用来衡量和选择最好的“分叉点”来进…

Adobe Dimension(DN)安装包软件下载

目录 一、软件简介 二、软件下载 三、注意事项 四、软件功能 五、常用快捷键 快捷键&#xff1a; 一、软件简介 Adobe Dimension&#xff08;简称DN&#xff09;是Adobe公司推出的一款三维设计和渲染软件。与一般的3D绘图软件相比&#xff0c;DN在操作界面和功能上有所不…

Spark实时(一):StructuredStreaming 介绍

文章目录 StructuredStreaming 介绍 一、SparkStreaming实时数据处理痛点 1、复杂的编程模式 2、SparkStreaming处理实时数据只支持Processing Time 3、微批处理&#xff0c;延迟高 4、精准消费一次问题 二、StructuredStreaming概述 三、​​​​​​​​​​​​​​…

python使用 tkinter 生成随机颜色

先看效果: 只要不停点击底部的按钮&#xff0c;每次都会生成新的颜色。炫酷啊。 import random import tkinter import tkinter.messagebox from tkinter import Button# todo """ 1. 设置一个按钮&#xff0c;来让用户选择是否显示颜色值 2. 把按钮换成 Label…

《白话机器学习的数学》第2章——学习回归

2.1设置问题 1.机器学习所做的事情正是从数据中进行学习&#xff0c;然后给出预测值。 2.2定义模型 1.一次函数的表达式&#xff1a; 其中θ叫做参数。 在统计学领域&#xff0c;人们常常使用 θ 来表示未知数和推测值。采用 θ加数字下标的形式&#xff0c;是为了防止当未知数…

网络访问(Socket/WebSocket/HTTP)

概述 HarmonyOS为用户提供了网络连接功能&#xff0c;具体由网络管理模块负责。通过该模块&#xff0c;用户可以进行Socket网络通滚、WebSocket连接、HTTP数据请求等网络通信服务。 Socket网络通信&#xff1a;通过Socket(嵌套字)进行数据通信&#xff0c;支持的协议包括UDP核…

【教程】vscode添加powershell7终端

win10自带的 powershell 是1.0版本的&#xff0c;太老了&#xff0c;更换为powershell7后&#xff0c;在 vscode 的集成终端中没有显示本篇教程记录在vscode添加powershell7终端的过程 打开vscode终端配置 然后来到这个页面进行设置 查看 powershell7 的安装位置&#xff…

JDK HttpClient - Java 11 可用的 JDK 内置的 HTTP 客户端

在 Java 应用的开发中&#xff0c;发送 HTTP 请求是一个常见的需求。应用在开发时&#xff0c;通常会使用流行的开源第三方库作为 HTTP 客户端&#xff0c;如 Apache HttpClient 或 OkHttp 等。这里介绍的是 JDK 自带的 HttpClient 实现&#xff0c;Java 11 可用。说到这里&…