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.mzph.cn/pingmian/49163.shtml

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

相关文章

vue项目实战速查记录

1.图片下载到本地 2.本地静态文件访问 3.元素大小相同,相互覆盖 1.图片下载到本地 实现原理:创建a标签,利用a标签下载属性. download(){const link document.createElement(a);link.href "图片地址";link.setAttribute(download, name);document.body.ap…

读论文《Hi-Net: Hybrid-fusion Network for Multi-modalMR Image Synthesis》

论文题目&#xff1a;Hi-Net:用于多模态磁共振图像合成的混合融合网络 论文地址&#xff1a;arxiv 项目地址&#xff1a;github 原项目可能在训练的时候汇报version的错&#xff0c;这是因为生成器和辨别器的优化有些逻辑错误&#xff0c;会改的话多加一个生成操作可以解决&…

React 学习——条件渲染、遍历循环、事件绑定

React特点&#xff1a; 声明式的设计高效&#xff0c;采用虚拟DOM来实现DOM的渲染&#xff0c;最大限度减少DOM的操作灵活&#xff0c;跟其他库灵活搭配使用JSX&#xff0c;俗称JS里面写HTML&#xff0c;JavaScript语法的扩展组件化&#xff0c;模块化&#xff0c;代码容易复用…

pdf的下载,后端返回工作流,前端进行转换

前端将后端返回的工作流进行转换 项目中接触到了pdf的下载和预览的功能&#xff0c;记录一下~ 这里pdf的下载和预览的接口&#xff0c;后端返回的数据结构与其他的接口返回的数据结构有点不同&#xff0c;是直接返回的工作流&#xff0c;在控制台接口的响应预览内容大致是这样…

初学MySQl简单sql语句(1)

目录 SQL语句介绍&#xff1a; DDL创建数据库&#xff1a; char和varchar比较 数值类型 数据库存储引擎 数据库存储引擎——InnoDB 数据库存储引擎——MyISAM 数据库存储引擎-MyISAM 和InnoDB区别 修改和删除数据库表 数据库设计三大范式 一、什么是范式 二、约束作…

css实战案例1:顶部搜索

代码样式&#xff1a; <!DOCTYPE html> <html><head><meta charset"utf-8" /><title></title></head><body><div class"search_box"><!-- 搜索框--><div class"search">搜索…

【Linux】网络基础_2

文章目录 十、网络基础2. IP地址和MAC地址3. 端口号端口号和进程ID 4. 网络字节序 未完待续 十、网络基础 2. IP地址和MAC地址 IP协议有两个版本&#xff0c;IPv4和IPv6&#xff0c; 用的比较多的都是IPv4。IP地址是在IP协议中&#xff0c;用来标识网络中不同主机的地址&…

如何发现快速发现分析生产问题SQL

Performance Schema介绍 Performance Schema提供了有关MySQL服务器内部运行的操作上的底层指标。为了解释清楚Performance Schema的工作机制&#xff0c;先介绍两个概念。 第一个概念是程序插桩&#xff08;instrument&#xff09;。程序插桩在MySQL代码中插入探测代码&#xf…

基本聚集函数和case的应用

文章目录 1.基本聚集函数(1)基本聚集函数的介绍(2)使用基本聚集函数的简单例子&#xff08;1&#xff09;查询最大年龄&#xff0c;最小年龄年龄和平均年龄<1>最大年龄<2>最小年龄<3>平均年龄 (2&#xff09;配合上where语句&#xff0c;查询女士的平均年龄(…

JAVA笔记十四

十四、集合 1.集合概述 (1)集合是存储其它对象的特殊对象&#xff0c;可以将集合当作一个容器 (2)集合的相关接口和类位于java.util包中 (3)集合中的接口和类是一个整体、一个体系 2.集合接口 接口定义了一组抽象方法&#xff0c;实现该接口的类需要实现这些抽象方法&…

Docker核心技术:Docker原理之Cgroups

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 Docker核心技术 系列文章&#xff1a;Docker原理之Cgroups&#xff0c;其他文章快捷链接如下&#xff1a; 应用架构演进容器技术要解决哪些问题Docker的基本使用Docker是如何实现的 Docker核心技术&#xff1a;…

C++初学者指南-5.标准库(第一部分)--标准库最小/最大算法

C初学者指南-5.标准库(第一部分)–标准库min/max算法 文章目录 C初学者指南-5.标准库(第一部分)--标准库min/max算法minmaxminmaxclamp (C17)min_elementmax_elementminmax_element相关内容 C标准库算法是一块新领域&#xff1f;⇒简短介绍 min min(a, b) → a 如果 a < b则…

Linux_实现UDP网络通信

目录 1、实现服务器的逻辑 1.1 socket 1.2 bind 1.3 recvfrom 1.4 sendto 1.5 服务器代码 2、实现客户端的逻辑 2.1 客户端代码 3、实现通信 结语 前言&#xff1a; 在Linux下&#xff0c;实现传输层协议为UDP的套接字进行网络通信&#xff0c;网络层协议为IPv4&am…

k8s+containerd(kvm版)

k8s&#xff08;Kubernetes&#xff09;是由Gogle开源的容器编排引擎&#xff0c;可以用来管理容器化的应用程序和服务&#xff0c;k 高可用&#xff1a;系统在长时间内持续正常地运行&#xff0c;并不会因为某一个组件或者服务的故障而导致整个系统不可用可扩展性&#xff1a…

【SpringBoot】 jasypt配置文件密码加解密

目前我们对yml配置文件中的密码都是明文显示&#xff0c;显然这不安全&#xff0c;有的程序员离职了以后可能会做一些非法骚操作&#xff0c;所以我们最好要做一个加密&#xff0c;只能让领导架构师或者技术经理知道这个密码。所以这节课就需要来实现一下。 我们可以使用jasypt…

爬虫学习3:爬虫的深度爬取

爬虫的深度爬取和爬取视频的方式 深度爬取豆瓣读书 import time import fake_useragent import requests from lxml import etree head {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 …

陶德:边种田边写代码,3年300万行,一个人写出了“国产大满贯QT”

这是《开发者说》的第12期&#xff0c;本期我们邀请的开发者是陶德&#xff0c;从小在国企矿山里长大&#xff0c;计算机成绩是文科班里最差的一个&#xff0c;毕业两年找不到工作&#xff0c;睡过公园&#xff0c;讨过剩饭&#xff0c;用打魔兽世界的方式磨炼技术&#xff0c;…

.NET 8+Vue2 部署到Window Server

.NET 8Vue2 部署到Window Server 1 配置环境 1.1 下载安装.NET 8 SDK&#xff0c;下载时需注意目标服务器的系统类型&#xff08;64位 or 32位&#xff09; https://dotnet.microsoft.com/zh-cn/download 1.2 下载安装SQL Server数据库&#xff08;服务和管理工具&#xff…

海外短剧系统搭建开发定制,H5/APP源码搭建部署,支持二开

目录 前言&#xff1a; 一、系统功能 二、部署流程 前言&#xff1a; 海外短剧系统搭建部署&#xff0c;前端uniapp&#xff0c;PHP语言。支持二开功能。 一、系统功能 以下是改写后的内容&#xff1a; 1. 多语言环境集成 —— 提供一键式翻译功能&#xff0c;轻松切换多…