协程模式在Android中的应用及工作原理

协程模式在Android中的应用及工作原理

在Android开发中,很多开发者通过代码模式学习协程,通常这已经足够应付了。但这种学习方式忽略了协程背后的精髓,事实上,它们的原理非常简单。那么,是什么使得这些模式起作用呢?

拿起你的工具,我们来揭开一些常见的协程模式,这些模式你可能已经见过很多次,并惊叹于它们背后的奥妙。

当然,如果你对协程还不太熟悉,那么欢迎!以下是一些对Android开发者来说非常值得学习的模式。

模式1:挂起函数

就像做吐司一样简单。你可能已经知道了:

  1. 把面包放进烤箱里。
  2. 等一会儿。
  3. 从烤箱里拿出烤好的面包。

下面是用Kotlin写的这个过程:

suspend fun makeToast() {
    println("把面包放进烤箱")
    delay(2000)
    println("面包现在是烤好的了")
}

如果你回顾一下整个过程,你会发现你大部分时间都是在等待面包变成烤面包。只有很少的时间你真正在活动。

那么在等待的时候你可以做些什么呢?嗯,任何你喜欢的事情。你可以在待办事项上勾掉另一项任务。只要你及时回来处理烤好的面包,就没问题。

这就是挂起函数的作用。在等待的过程中,协程被挂起,这告诉协程库(具体来说是调度器)它可以做其他的事情。

所以,这是关键部分——当你调用这个挂起函数时,底层线程并没有被阻塞。协程库高效地利用了等待的时间,让线程继续工作。

当然,对于调用上面的makeToast()函数的代码来说,这些细节并不重要。你调用makeToast(),函数稍后返回,一旦面包烤好,它就会通知你。无论它是坐着等面包,还是做其他工作,都不影响你的调用。

模式2:从主线程调用挂起函数

这就是为什么通常可以安全地从主/UI线程调用挂起函数的原因。因为挂起函数不会阻塞主线程,所以主线程可以继续进行UI操作。

下面是一个示例。点击按钮后,我们会显示一个PIN码10秒钟,然后再隐藏它:

//MainActivity.kt
@Composable
fun PlanetsScreen(...) {
    val revealPIN by viewModel.isShowingPin.collectAsStateWithLifecycle()
    val scope = rememberCoroutineScope()

    Column {
        Button(
            onClick = {
                scope.launch {
                    // Here we call a function which takes at least 10 seconds to run,
                    // directly from the main thread. Safe because the thread isn't blocked.
                    viewModel.revealPinBriefly()
                }
            }
        ) {
            Text("Reveal PIN")
        }

        if (revealPIN) {
            Text(text = "Your PIN is 1234")
        }
    }
}
//MyViewModel.kt
val isShowingPin = MutableStateFlow(false)

// This function suspends the coroutine for a long time, but
// doesn't block the calling thread. So it can be called from
// the main/UI thread safely.
suspend fun revealPinBriefly() {
    isShowingPin.value = true
    delay(10_000)
    isShowingPin.value = false
}

这是完全安全的,因为它不会阻塞用户界面线程。在这10秒的延迟期间,用户界面仍然可以响应。

模式3:切换上下文

许多挂起函数大部分时间都是处于挂起状态。一个很好的例子是从互联网获取数据:建立连接很容易,但等待数据下载占据了大部分时间。

那么,在用户界面线程上执行挂起的网络任务是否安全?不!根本不安全。

调用线程只在挂起任务实际被挂起的时间内(即等待期间)解除阻塞。

网络任务涉及各种等待之外的工作:设置连接、加密、解析响应等。它们可能只需要几毫秒的时间,但这是用户界面线程被阻塞的几毫秒。

出于性能原因,你需要确保用户界面线程持续更新界面。不要中断它,否则你的应用程序性能会受到影响。

因此,我们有了“切换上下文”的模式:

//NotesRepository.kt
suspend fun saveNote(note: Note) {
    withContext(Dispatchers.IO) {
        notesRemoteDataSource.saveNote(note)
    }
}

上面的withContext确保该挂起函数在IO线程池上运行。有了这个设置,可以安全地从用户界面线程调用saveNote函数。

作为一个通用规则:确保挂起函数在需要时切换上下文,以便可以从用户界面线程调用它们。

模式4:在作用域中运行协程

这不是一个具体的模式,因为所有的协程都需要在某个上下文中运行。

但以下面的例子为例,像这样的代码实际上是什么意思?

viewModelScope.launch {
  // Do something
}

让我们从简单的角度来看:协程的作用域表示它的生命周期。实际上还有更多细节,我会在以后的文章中详细介绍,但这是一个很好的起点。

所以,通过使用viewModelScope.launch,你是在说:“启动一个协程,它的生命周期受到viewModelScope的限制”。

因此,这里的viewModelScope就像是一个容器,用来保存View Model的协程,包括上面的那个协程。当容器被清空时——也就是当viewModelScope被取消时,其中的内容也将被取消。以实际情况来说,这意味着你可以编写代码,而不必担心何时关闭它。

模式5:在挂起函数中执行多个操作

我们首先接触到了viewModelScope。还有许多其他的,例如:

在Compose中有rememberCoroutineScope(),它提供了一个作用域,持续时间与@Composable在屏幕上的时间相同。(上面的模式1有一个示例)
在Android视图中有viewLifecycleOwner.lifecycleScope,它持续时间与Activity/Fragment相同
GlobalScope永远持续(因此通常是一个不好的主意™,但并非总是如此)
或者,你可以像下面这个模式一样自己创建:

//NotesRepository.kt 
suspend fun deleteAllNotes() = withContext(...) {
    // Create a scope. The suspend function will return when *all* the
    // scope's child coroutines finish.
    coroutineScope {
        launch { remoteDataSource.deleteAllNotes() }
        launch { localDataSource.deleteAllNotes() }
    }
}

那么为什么你想这样做呢?well,coroutineScope是一个特殊的函数,它创建一个新的协程作用域,并挂起,直到它内部的所有子协程都完成。

所以上面的模式意味着“并行执行这些任务,当它们全部完成时再返回”。

例如,在具有本地和远程数据源的仓库类中,这非常有用,因为你经常希望同时对两个数据源执行某些操作。只有当两个操作都完成时,才认为该操作已完成。

模式6:无限循环

现在我们理解了协程作用域,我们可以看到为什么这种模式实际上是可行的:

//MyViewModel.kt
fun flashTheLights() {
    viewModelScope.launch {
        // This seems like an unsafe infinite loop, but in fact
        // it'll shut down when the viewModelScope is cancelled.
        while(true) {
            delay(1_000)
            lightState = !lightState
        }
    }
}

在5年前,while(true)这样的代码会被认为是一个巨大的问题,但在这种情况下实际上是安全的。一旦viewModelScope被取消,启动的协程也会被取消,这样这个“无限”循环就会停止。

但它停止的原因非常有趣…

调用delay()函数会让出线程给协程调度器。这意味着它允许协程调度器检查是否有其他任务需要执行,并且可以进行处理。

但同时,协程调度器也会检查协程是否已被取消,如果是的话,会抛出CancellationException异常。虽然你不需要对此异常进行处理,但结果是堆栈展开,while(true)这部分的代码会被丢弃。

反模式:一个不会挂起的挂起函数

因此,让出线程给协程调度器是非常重要的。你可以放心地使用Room、Retrofit和Coil等库,因为它们会在需要时将任务交给调度器处理。

但这也是为什么永远不应该编写这样的协程代码的原因:

//main.kt
// !!!!! DON'T DO THIS !!!!!
suspend fun countToAHundredBillion_unsafe() {
    var count = 0L
    
    // This suspend fun won't be cancelled if the coroutine
    // that's running it gets cancelled, because it doesn't
    // ever yield.
    while(count < 100_000_000_000) {
        count++
    }
}

这个程序需要很长时间才能运行完毕。而且一旦开始,就无法停止。

为了确保协程的安全性,可以使用yield()函数。yield()有点像运行delay()函数,但并不会真正延迟执行,它会让出给调度器,并在需要停止时接收到CancellationException异常。

下面是一个安全版本的函数:

//main.kt
suspend fun countToAHundredBillion() {
    var count = 0L
    
    while(count < 100_000_000_000) {
        count++
        
        // Every 10,000 we yield to the coroutine
        // dispatcher, allowing this loop to be
        // cancelled if needed.
        if (count % 10_000 == 0) {
            yield()
        }
    }
}

所以,这里总共有六种使用协程的模式和一种反模式。最重要的是,我们了解了它们为什么有效以及背后的原理。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot整合Flowable最新教程(一)Flowable介绍

一、Flowable 入门介绍 代码实现文章&#xff1a;SpringBoot整合Flowable最新教程&#xff08;二&#xff09; 官网地址&#xff1a;https://www.flowable.org/   Flowable6.3中文教程&#xff1a;中文教程地址   可以在官网下载对应的jar包在本地部署运行&#xff0c;官方…

Qt设计师中(没有现成的控件):如何添加QToolBar工具栏

1、在QtCreator设计师界面中,在MainWindow上右键,有“添加工具栏”菜单项 2、但只有在MainWindow上右键才有&#xff0c;在其它控件上方点击则没有&#xff0c;那么怎么在对话框上添加呢&#xff1f; 可以添加一个QWidget&#xff0c;然后手动在ui文件里把class改为QToolBar就…

PS一键磨皮插件Delicious Retouch for mac中文 支持PS2024

Delicious Retouch for Mac是一款优秀的Photoshop插件&#xff0c;专注于人像修饰。以下是该插件的一些主要特点和功能&#xff1a; 软件下载&#xff1a;Delicious Retouch for mac中文 支持PS2024 人像修饰工具&#xff1a;Delicious Retouch专注于人像修饰&#xff0c;提供了…

react和antd学习笔记

概论 react是前端框架&#xff0c;antd是组件库。前端框架和组件库的区别与联系 nodejs 脚本语言需要一个解析器才能运行&#xff0c;JavaScript是脚本语言&#xff0c;在不同的位置有不一样的解析器&#xff0c;如写入html的js语言&#xff0c;浏览器是它的解析器角色。而对…

寒假 day4

1.请编程实现哈希表的创建存储数组(12,24,234,234,23,234,23),输入key查的值&#xff0c;实现查找功能。 #include<stdio.h> #include<string.h> #include<stdlib.h> #include<math.h> typedef int datatype; typedef struct Node {//数据域datatype…

总结—elasticsearch启动失败的几种情况及解决

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 摘要 本文主要梳理从ES初学以来所遇到的启动失败的几种情况。 1、使用root用户启动失败 在有一次搭建elasticsearch的时候&am…

16-Verilog实现二线制I2C CMOS串行EEPROM的读写操作

Verilog实现二线制I2C CMOS串行EEPROM的读写操作 1&#xff0c;二线制I2C CMOS串行EEPROM的简单介绍2&#xff0c;I2C总线特征介绍3&#xff0c;二线制I2C、CMOS串行EEPROM的读写操作4&#xff0c;EEPROM的Verilog HDL程序4.1&#xff0c;EEPROM的行为模型思路如下&#xff1a;…

Jmeter性能测试: Jmeter 5.6.3 分布式部署

目录 一、实验 1.环境 2.jmeter 配置 slave 代理压测机 3.jmeter配置master控制器压测机 4.启动slave从节点检查 5.启动master主节点检查 6.运行jmeter 7.观察jmeter-server主从节点变化 二、问题 1.jmeter 中间请求和响应乱码 一、实验 1.环境 &#xff08;1&#…

kubesphere部署k8s-v1.23.10

功能&#xff1a; &#x1f578; 部署 Kubernetes 集群 &#x1f517; Kubernetes 多集群管理 &#x1f916; Kubernetes DevOps &#x1f50e; 云原生可观测性 &#x1f9e9; 基于 Istio 的微服务治理 &#x1f4bb; 应用商店 &#x1f4a1; Kubernetes 边缘节点管理 &#x1…

[职场] 财务共享是什么 #笔记#笔记#知识分享

财务共享是什么 财务共享作为一种创新的财务管理模式&#xff0c;已经得到了我国众多企业的认可和实践。通过实施财务共享&#xff0c;企业可以有效提高财务管理效率&#xff0c;降低成本&#xff0c;优化资源配置&#xff0c;从而为企业的可持续发展提供有力保障。本文会进行…

myql 项目数据库和表的设计

1.表的设计和创建 2.在navicate运行这些代码 create table user(id int not null auto_increment primary key,name varchar(50) not null unique,password varchar(50) not null,state enum(online,offline) default offline ); create table friend(userid int not null,…

操作系统基础:文件系统基础【下】

&#x1f308;个人主页&#xff1a;godspeed_lucip &#x1f525; 系列专栏&#xff1a;OS从基础到进阶 ⚔️1 文件的基本操作⚖️1.1 总览⚖️1.2 几种基本操作&#x1f52d;1.2.1 创建文件&#x1f52d;1.2.2 删除文件&#x1f52d;1.2.3 打开文件&#x1f52d;1.2.4 关闭文件…

mysql:事务的特性ACID、并发事务(脏读、不可重复读、幻读、如何解决、隔离级别)、undo log和redo log的区别、相关面试题和答案

事务是一组操作的集合&#xff0c;它会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 事务的特性&#xff08;ACID&#xff09; 原子性&#xff08;Atomicity&#xff09;&#xff1a;事务是不可分割的…

React16源码: React中处理hydrate的核心流程源码实现

hydrate 1 &#xff09;概述 hydrate 在react当中不算特别重要, 但是很多时候会用到的一个API这个 API 它主要作用就是在进入第一次渲染的时候&#xff0c;如果本身 dom 树上面已经有一个dom结构存在是否可以去利用这一部分已经存在的dom&#xff0c;然后去避免掉在第一次渲染…

2024/2/4 备战蓝桥杯 5-1 前缀和

目录 求和 0求和 - 蓝桥云课 (lanqiao.cn) 可获得的最小取值 0可获得的最小取值 - 蓝桥云课 (lanqiao.cn) 领地选择 P2004 领地选择 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 求和 0求和 - 蓝桥云课 (lanqiao.cn) 思路&#xff1a;先对公式进行合并同类相&#x…

浅压缩、深压缩、双引擎、计算机屏幕编码……何去何从?

专业视听领域尤其显示控制和坐席控制领域&#xff0c;最近几年最激动人心的技术&#xff0c;莫过于分布式了。 分布式从推出之日就备受关注&#xff1a;担心稳定性的&#xff0c;质疑同步性能的&#xff0c;怀疑画面质量的…… 诚然&#xff0c;我们在此前见多了带着马赛克的…

windows安装Visual Studio Code,配置C/C++运行环境(亲测可行)

一.下载 Visual Studio Code https://code.visualstudio.com/ 二.安装 选择想要安装的位置: 后面的点击下一步即可。 三.下载编译器MinGW vscode只是写代码的工具&#xff0c;使用编译器才能编译写的C/C程序&#xff0c;将它转为可执行文件。 MinGW下载链接&#xff1a;…

day31 JS执行机制

目录 前言同步和异步JS执行机制 前言 JavaScript语言的一大特点是单线程。 JavaScript是为处理页面中用户的交互&#xff0c;以及操作DOM而诞生的。比如对某个DOM元素进行添加和删除操作&#xff0c;不能同时进行&#xff0c;应该先进行添加再继续删除。 示例&#xff08;解…

【日常总结】SourceTree 1.5.2.0 更换用户名称和密码

一、场景 二、问题 三、解决方案 > 方案一&#xff1a;删除缓存文件 > 方案二&#xff1a;更新最新版本&#xff0c;可以直接修改密码&#xff08;推荐&#xff09; 方案一&#xff1a;删除缓存文件 Stage 1&#xff1a;设置显示隐藏文件 Stage 2&#xff1a;打开…

基于时频分析的SAR目标微波视觉特性智能感知方法与应用

源自&#xff1a;雷达学报 作者&#xff1a;黄钟泠, 吴冲, 姚西文 “人工智能技术与咨询” 发布 摘 要 合成孔径雷达(SAR)目标识别智能算法目前仍面临缺少鲁棒性、泛化性和可解释性的挑战&#xff0c;理解SAR目标微波特性并将其结合先进的深度学习算法&#xff0c;实现高效…