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;代码容易复用…

git 过滤LFS文件下载

git config --global filter.lfs.smudge "git-lfs smudge --skip -- %f" git config --global filter.lfs.process "git-lfs filter-process --skip" 恢复下载 git config --global filter.lfs.smudge "git-lfs smudge -- %f" git config --g…

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

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

南京移动老年手机课堂助力社区老人智享生活

为帮助社区老年群体解决使用智能手机过程中遇到的问题&#xff0c;让老年人充分享受数字化、智能化带来的便利&#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;用来标识网络中不同主机的地址&…

Android SurfaceFlinger——关联EGL三要素(二十七)

通过前面的文章我们得到了 EGL 的三要素——Display、Surface 和 Context。其中,Display 是一个图形显示系统或者硬件屏幕,Surface 代表一个可以被渲染的图像缓冲区,Context 包含了 OpenGL ES 的状态信息和资源,它是执行 OpenGL 命令的环境。下一步就是调用 eglMakeCurrent…

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

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

启动金运项目的方法

1.前端工程在Terminal总npm run serve 2.mongoDB数据库打开&#xff0c;详情见Docker进入MongoDB-CSDN博客 3.后端工程把SpringApplication播放起来

谷粒商城实战笔记-包依赖踩坑

一&#xff0c; java.lang.ClassNotFoundException: org.springframework.boot.context.properties.ConfigurationBeanFactoryMetadata ClassNotFoundException类似错误&#xff0c;都是依赖问题&#xff0c;特别是模块比较多&#xff0c;有模块相互依赖、父子依赖&#xff0c…

目标检测的即时演进:在线学习在行动

目标检测的即时演进&#xff1a;在线学习在行动 在线学习&#xff08;Online Learning&#xff09;是一种机器学习范式&#xff0c;它允许模型通过逐步接收数据并实时更新来学习。这种学习方式对于目标检测尤其重要&#xff0c;因为它允许检测系统在不断变化的环境中适应新的或…

【Rust光年纪】探究Rust异步I/O库:高性能网络应用的选择指南

构建高性能网络应用&#xff1a;Rust异步编程库全方位解析 前言 随着互联网的快速发展&#xff0c;构建高性能和可扩展的网络应用程序变得愈发重要。而异步I/O库和异步编程库在这一领域扮演着至关重要的角色。本文将对几种用于Rust语言的异步I/O库和异步编程库进行介绍和比较…

基本聚集函数和case的应用

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

挽民族星光,寻家乡特色

2024年7月15日 西安工程大学计算机科学学院“筑梦乡村&#xff0c;携手同行”赴马家乔蒸面进行进一步探索调查。 实践队伍首先来到蒸面馆初步观察&#xff0c;店铺不大客流量却很多&#xff0c;虽忙的不可开交服务态度却格外热情&#xff0c;紧接着上楼参观了蒸面的制作流程与基…

Python polars学习-09 数据框关联与拼接

背景 polars学习系列文章&#xff0c;第9篇 数据框关联与拼接&#xff08;Join 、Concat&#xff09; 该系列文章会分享到github&#xff0c;大家可以去下载jupyter文件&#xff0c;进行参考学习 仓库地址&#xff1a;https://github.com/DataShare-duo/polars_learn 小编运…

2024前端面试真题【手写篇】

求几个数的总和&#xff08;2&#xff0c;3&#xff0c;4&#xff0c;5&#xff0c;6&#xff0c;6&#xff0c;7&#xff0c;7&#xff0c;8&#xff0c;8&#xff0c;8&#xff09; arr.reduce((total, currentVal, currentInd, arr)>{}, initialVal) const arr [2&#…

C++:智能指针 [unique_ptr]

文章目录 0x1 基本使用0x11 get() [ 参照auto_ptr ]0x12 release() [ 参照auto_ptr ]0x13 reset() [ 参照auto_ptr ]0x14 get_deleter() [ 新增 ]0x15 operator bool [ 新增 ]0x16 swap() [ 新增 ]0x2 make_unique函数 [unique_ptr在C11引入&#xff0c;make_unique在C14引入]0…