Kotlin 协程 — 基础

news2024/12/25 9:36:10

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: CoroutineScope
        doWorld()
    }
    
    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) { // 将被派发到 DefaultDispatcher
        println("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.coloradmin.cn/o/1945456.html

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

相关文章

【MySQL进阶之路 | 高级篇】事务的ACID特性

1. 数据库事务概述 事务是数据库区别于文件系统的重要特性之一&#xff0c;当我们有了事务就会让数据库始终保持一致性&#xff0c;同时我们还能通过事务的机制恢复到某个时间点&#xff0c;这样可以保证给已提交到数据库的修改不会因为系统崩溃而丢失。 1.1 基本概念 事务&…

企业微信获客助手广告平台深度回传/双回传设置教程参考

很多商家在使用【转化宝】进行推广时只采用了单回传&#xff0c;其实很多情况下单回传即可满足推广模型优化需求&#xff1b;但是最近很多专业化广告运营的代投或运营都开始采用双回传&#xff0c;【转化宝】支持抖音巨量引擎、百度营销广告、快手广告、腾讯广告等均支出深度优…

微信小程序开发:项目程序代码构成

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

开源模型应用落地-LangChain高阶-记忆组件-ConversationBufferMemory正确使用(一)

一、前言 LangChain 的记忆组件发挥着至关重要的作用&#xff0c;其旨在协助大语言模型&#xff08;LLM&#xff09;有效地留存历史对话信息。通过这一功能&#xff0c;使得大语言模型在对话过程中能够更出色地维持上下文的连贯性和一致性&#xff0c;进而能够像人类的记忆运作…

【网络安全】构建稳固与安全的网络环境:从“微软蓝屏”事件中汲取的教训

发生什么事了&#xff1f; 近日&#xff0c;一次由微软视窗系统软件更新引发的全球性“微软蓝屏”事件&#xff0c;不仅成为科技领域的热点新闻&#xff0c;更是一次对全球IT基础设施韧性与安全性的深刻检验。这次事件&#xff0c;源于美国电脑安全技术公司“众击”提供的一个…

中断和EXIT原理介绍

中断和EXIT原理介绍 一、中断的介绍&#xff1f;二、EXIT的介绍1.EXIT作用2.EXIT的详情3.EXIT中AFIO复用的作用4.STM32中AFIO复用作用 一、中断的介绍&#xff1f; 二、EXIT的介绍 EXTI&#xff08;Extern Interrupt&#xff09;外部中断 1.EXIT作用 EXTI可以监测指定GPIO口…

Java学习笔记(五)数组、冒泡排序

Hi i,m JinXiang ⭐ 前言 ⭐ 本篇文章主要介绍Java数组、冒泡排序使用以及部分理论知识 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主收将持续更新学习记录获&#xff0c;友友们有任何问题可以在评论区留言 …

力扣SQL50 指定日期的产品价格 双重子查询 coalesce

Problem: 1164. 指定日期的产品价格 coalesce 的使用 简洁版 &#x1f468;‍&#x1f3eb; 参考题解 select distinct p1.product_id,coalesce((select p2.new_pricefrom Products p2where p2.product_id p1.product_id and p2.change_date < 2019-08-16order by p2.…

Web前端Promise

Promise介绍与使用 Promise是什么&#xff1f; 1.抽象表达&#xff1a; Promise是一门新的技术&#xff08;ES6规范&#xff09;Promise是JS中进行异步编程的新解决方案备注&#xff1a;旧方案是单纯使用回调函数 2.具体表达&#xff1a; 从语法上来说&#xff1a;Promise…

xmind--如何快速将Excel表中多列数据,复制到XMind分成多级主题

每次要将表格中的数据分成多级时&#xff0c;只能复制粘贴吗 快来试试这个简易的方法吧 这个是原始的表格&#xff0c;分成了4级 步骤&#xff1a; 1、我们可以先按照这个层级设置下空列&#xff08;后买你会用到这个空列&#xff09; 二级不用加、三级前面加一列、四级前面加…

前端:Vue学习 - 购物车项目

前端&#xff1a;Vue学习 - 购物车项目 1. json-server&#xff0c;生成后端接口2. 购物车项目 - 实现效果3. 参考代码 - Vuex 1. json-server&#xff0c;生成后端接口 全局安装json-server&#xff0c;json-server官网为&#xff1a;json-server npm install json-server -…

C++ 设计模式(五)——状态模式

状态模式 序言理解源码 序言 设计模式只是一个抽象的设计模式方法&#xff0c;并不是一个固定使用的搭配&#xff0c;就算是普通switch语句&#xff0c;Map&#xff0c;乃至状态机都是状态模式的其中一种实现方法 状态模式看起来好像和策略模式差不多&#xff0c;主要是其的侧…

JavaScript构造函数小挑战

// 编码挑战 #1 /* 使用构造函数实现一辆汽车。一辆汽车有一个品牌和一个速度属性。speed 属性是汽车当前的速度&#xff0c;单位为 km/h&#xff1b; a. 执行一个 “accelerate ”方法&#xff0c;将汽车的速度提高 10&#xff0c;并将新速度记录到控制台&#xff1b; 3. a.…

若依Vue前后端分离版如何部署(windows)(超详细)

一、项目环境准备 下面是项目所需要准备的环境 Node.js redis 1、Node.js下载 下面进入官网可以下载Node.js — 在任何地方运行 JavaScript (nodejs.org)https://nodejs.org/zh-cn 下载完成安装后&#xff0c;需要配置环境变量&#xff0c;首先复制以下nodejs的安…

商汤提出的BRECQ量化框架是个什么?

商汤提出的BRECQ量化框架是个什么&#xff1f; 引言 近年来&#xff0c;深度学习在多个领域取得了显著进展&#xff0c;但其巨大的计算成本和内存占用问题逐渐凸显。为了压缩和加速已训练好的网络&#xff0c;量化成为了一种重要的技术手段。量化主要分为两类&#xff1a;量化…

DAMA学习笔记(七)-数据集成和互操作

1.引言 数据集成和互操作(DII)描述了数据在不同数据存储、应用程序和组织这三者内部和之间进行移动和整合的相关过程。数据集成是将数据整合成物理的或虚拟的一致格式。数据互操作是多个系统之间进行通信的能力。数据集成和互操作的解决方案提供了大多数组织所依赖的基本数据管…

数据库解析一维和二维简易JSON,

项目还在使用Oracle11&#xff0c;不支持后续官方的json解析方式&#xff0c; 在 前年、去年、今年 接连 遇到json解析问题后&#xff08;其实是公司的轮子效率太慢&#xff0c;太复杂&#xff0c;决定自己造个轮子&#xff0c;看看到底为什么慢&#xff0c;是不是真的很复杂&a…

计算机网络八股文(四)

目录 61.客户端调用close()后的断开流程是怎样的&#xff1f; 62.没有accept可以建立TCP连接吗&#xff1f; 63.没有listen可以建立TCP连接吗&#xff1f; 64.什么是TCP半连接队列&#xff08;SYN队列&#xff09;和全连接队列&#xff08;accept队列&#xff09;&#xff…

质量问题到底是谁的责任?

在竞争日益激烈的市场环境中&#xff0c;产品质量是企业生存与发展的基石。每当谈及生产企业的质量问题&#xff0c;我们往往不由自主地思考&#xff1a;在这一复杂而精细的生产链条中&#xff0c;究竟是谁该为质量问题负责&#xff1f; 必须明确一个无可争议的事实&#xff1…

MySQL多实例的配置

步骤1 环境准备 安装多实例数据库 1&#xff09;安装好数据库mariadb yum -y install mariadb-server 2&#xff09;创建mysql多实例数据文件目录 mkdir /data/mysql/3307/{data,etc,socket,log,bin,pid} -pv mkdir /data/mysql/3308/{data,etc,socket,log,bin,pid} –pv ch…