Kotlin:协程基础

点击查看:协程基础 中文文档
点击查看:协程基础 英文文档

第一个协程程序
import kotlinx.coroutines.*fun main(){GlobalScope.launch {delay(1000L)//delay 是一个特殊的 挂起函数 ,它不会造成线程阻塞,但是会 挂起 协程,并且只能在协程中使用。println("World!")}println("Hello - ")// 主线程中的代码会立即执行Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活//    runBlocking {//但是这个表达式阻塞了主线程
//        delay(2000L)//我们延迟 2 秒来保证 JVM 的存活
//    }
}

运行结果
在这里插入图片描述

本质上,协程是轻量级的线程。 它们在某些 CoroutineScope 上下文中与 launch 协程构建器 一起启动。 这里我们在 GlobalScope 中启动了一个新的协程,这意味着新协程的生命周期只受整个应用程序的生命周期限制。

可以将 GlobalScope.launch { …… } 替换为 thread { …… },并将 delay(……) 替换为 Thread.sleep(……) 达到同样目的。 试试看(不要忘记导入 kotlin.concurrent.thread)。
如果你首先将 GlobalScope.launch 替换为 thread,编译器会报以下错误:

Error: Kotlin: Suspend functions are only allowed to be called from a coroutine or another suspend function

这是因为 delay 是一个特殊的 挂起函数 ,它不会造成线程阻塞,但是会 挂起 协程,并且只能在协程中使用。

桥接阻塞与非阻塞的世界

第一个示例在同一段代码中混用了 非阻塞的 delay(……) 与 阻塞的 Thread.sleep(……)。 这容易让我们记混哪个是阻塞的、哪个是非阻塞的。 让我们显式使用 runBlocking 协程构建器来阻塞:

import kotlinx.coroutines.*/*** 协程基础** https://www.kotlincn.net/docs/reference/coroutines/basics.html** kotlin - Coroutine 协程 https://www.jianshu.com/p/76d2f47b900d*/
fun main(){GlobalScope.launch {delay(1000L)//delay 是一个特殊的 挂起函数 ,它不会造成线程阻塞,但是会 挂起 协程,并且只能在协程中使用。println("World!")}println("Hello - ")// 主线程中的代码会立即执行
//    Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活runBlocking {//但是这个表达式阻塞了主线程delay(2000L)//我们延迟 2 秒来保证 JVM 的存活}
}

结果是相似的,但是这些代码只使用了非阻塞的函数 delay。 调用了 runBlocking 的主线程会一直 阻塞 直到 runBlocking 内部的协程执行完毕。

这个示例可以使用更合乎惯用法的方式重写,使用 runBlocking 来包装 main 函数的执行:

import kotlinx.coroutines.*fun main() = runBlocking<Unit> { // 开始执行主协程GlobalScope.launch { // 在后台启动一个新的协程并继续delay(1000L)println("World!")}println("Hello,") // 主协程在这里会立即执行delay(2000L)      // 延迟 2 秒来保证 JVM 存活
}

这里的 runBlocking { …… } 作为用来启动顶层主协程的适配器。 我们显式指定了其返回类型 Unit,因为在 Kotlin 中 main 函数必须返回 Unit 类型。

这也是为挂起函数编写单元测试的一种方式:

class MyTest {@Testfun testMySuspendingFunction() = runBlocking<Unit> {// 这里我们可以使用任何喜欢的断言风格来使用挂起函数}
}
等待一个作业

延迟一段时间来等待另一个协程运行并不是一个好的选择。让我们显式(以非阻塞方式)等待所启动的后台 Job 执行结束:

//等待一个作业
fun main() = runBlocking{val job = GlobalScope.launch { // 启动一个新协程并保持对这个作业的引用delay(1000L)println("World!")}println("Hello,")job.join() // 等待直到子协程执行结束
}

运行结果
在这里插入图片描述
现在,结果仍然相同,但是主协程与后台作业的持续时间没有任何关系了。好多了。

结构化的并发

协程的实际使用还有一些需要改进的地方。 当我们使用 GlobalScope.launch 时,我们会创建一个顶层协程。虽然它很轻量,但它运行时仍会消耗一些内存资源。如果我们忘记保持对新启动的协程的引用,它还会继续运行。如果协程中的代码挂起了会怎么样(例如,我们错误地延迟了太长时间),如果我们启动了太多的协程并导致内存不足会怎么样? 必须手动保持对所有已启动协程的引用并 join 之很容易出错。

有一个更好的解决办法。我们可以在代码中使用结构化并发。 我们可以在执行操作所在的指定作用域内启动协程, 而不是像通常使用线程(线程总是全局的)那样在 GlobalScope 中启动。

在我们的示例中,我们使用 runBlocking 协程构建器将 main 函数转换为协程。 包括 runBlocking 在内的每个协程构建器都将 CoroutineScope 的实例添加到其代码块所在的作用域中。 我们可以在这个作用域中启动协程而无需显式 join 之,因为外部协程(示例中的 runBlocking)直到在其作用域中启动的所有协程都执行完毕后才会结束。因此,可以将我们的示例简化为:

import kotlinx.coroutines.*fun main() = runBlocking { // this: CoroutineScopelaunch { // 在 runBlocking 作用域中启动一个新协程delay(1000L)println("World!")}println("Hello,")
}
作用域构建器

除了由不同的构建器提供协程作用域之外,还可以使用 coroutineScope 构建器声明自己的作用域。它会创建一个协程作用域并且在所有已启动子协程执行完毕之前不会结束。

runBlocking 与 coroutineScope 可能看起来很类似,因为它们都会等待其协程体以及所有子协程结束。 主要区别在于,runBlocking 方法会阻塞当前线程来等待, 而 coroutineScope 只是挂起,会释放底层线程用于其他用途。 由于存在这点差异,runBlocking 是常规函数,而 coroutineScope 是挂起函数。

可以通过以下示例来演示:

import kotlinx.coroutines.*fun main() = runBlocking { // this: CoroutineScopelaunch {delay(200L)println("Task from runBlocking")}coroutineScope { // Creates a coroutine scopelaunch {delay(500L)println("Task from nested launch")}delay(100L)println("Task from coroutine scope") // This line will be printed before the nested launch}println("Coroutine scope is over") // This line is not printed until the nested launch completes//打印结果//Task from coroutine scope//Task from runBlocking//Task from nested launch//Coroutine scope is over
}

运行结果
在这里插入图片描述
请注意,(当等待内嵌 launch 时)紧挨“Task from coroutine scope”消息之后, 就会执行并输出“Task from runBlocking”——尽管 coroutineScope 尚未结束。

提取函数重构

我们来将 launch { …… } 内部的代码块提取到独立的函数中。当你对这段代码执行“提取函数”重构时,你会得到一个带有 suspend 修饰符的新函数。 这是你的第一个挂起函数。在协程内部可以像普通函数一样使用挂起函数, 不过其额外特性是,同样可以使用其他挂起函数(如本例中的 delay)来挂起协程的执行。

import kotlinx.coroutines.*fun main() = runBlocking {launch { doWorld() }println("Hello,")
}// 这是你的第一个挂起函数
suspend fun doWorld() {delay(1000L)println("World!")
}

运行结果
在这里插入图片描述

但是如果提取出的函数包含一个在当前作用域中调用的协程构建器的话,该怎么办? 在这种情况下,所提取函数上只有 suspend 修饰符是不够的。为 CoroutineScope 写一个 doWorld 扩展方法是其中一种解决方案,但这可能并非总是适用,因为它并没有使 API 更加清晰。 惯用的解决方案是要么显式将 CoroutineScope 作为包含该函数的类的一个字段, 要么当外部类实现了 CoroutineScope 时隐式取得。 作为最后的手段,可以使用 CoroutineScope(coroutineContext),不过这种方法结构上不安全, 因为你不能再控制该方法执行的作用域。只有私有 API 才能使用这个构建器。

协程很轻量

运行下面的代码

import kotlinx.coroutines.*fun main() = runBlocking {repeat(100_000) { // 启动大量的协程launch {delay(5000L)print(".")}}
}

它启动了 10 万个协程,并且在 5 秒钟后,每个协程都输出一个点。

现在,尝试使用线程来实现。会发生什么?(很可能你的代码会产生某种内存不足的错误)

全局协程像守护线程

以下代码在 GlobalScope 中启动了一个长期运行的协程,该协程每秒输出“I’m sleeping”两次,之后在主函数中延迟一段时间后返回。

import kotlinx.coroutines.*suspend fun main() {GlobalScope.launch {repeat(1000) { i ->println("I'm sleeping $i ...")delay(500L)}}delay(1300L) // 在延迟后退出//输出结果//I'm sleeping 0 ...//I'm sleeping 1 ...//I'm sleeping 2 ...
}

运行结果
在这里插入图片描述

在 GlobalScope 中启动的活动协程并不会使进程保活。它们就像守护线程。

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

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

相关文章

react-JSX基本使用

1.目标 能够知道什么是JSX 能够使用JSX创建React元素 能够在JSX中使用JS表达式 能够使用JSX的条件渲染和列表渲染 能够给JSX添加样式 2.目录 JSX的基本使用 JSX中使用JS表达式 JSX的条件渲染 JSX的列表渲染 JSX的样式处理 3.JSX的基本使用 3.1 createElement()的问题 A. …

代码随想录算法训练营day24

题目&#xff1a;77. 组合 参考链接&#xff1a;代码随想录 回溯法理论基础 回溯三部曲&#xff1a;回溯函数模板返回值以及参数、回溯函数终止条件、回溯搜索的遍历过程。 模板框架&#xff1a; void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择&…

【算法与数据结构】复杂度深度解析(超详解)

文章目录 &#x1f4dd;算法效率&#x1f320; 算法的复杂度&#x1f320; 时间复杂度的概念&#x1f309;大O的渐进表示法。 &#x1f320;常见复杂度&#x1f320;常见时间复杂度计算举例&#x1f309;常数阶O(1)&#x1f309;对数阶 O(logN)&#x1f309;线性阶 O(N)&#x…

选择排序的简单介绍

选择排序是一种简单直观的排序算法&#xff0c;其原理如下&#xff1a; 1. 遍历数组&#xff0c;找到最小&#xff08;或最大&#xff09;的元素&#xff0c;并将其与数组的第一个元素交换位置。 2. 接着在剩下的元素中找到最小&#xff08;或最大&#xff09;的元素&#xff…

图论-算法题

797. 所有可能的路径 题目: 给你一个有 n 个节点的 有向无环图&#xff08;DAG&#xff09;&#xff0c;请你找出所有从节点 0 到节点 n-1 的路径并输出&#xff08;不要求按特定顺序&#xff09; graph[i] 是一个从节点 i 可以访问的所有节点的列表&#xff08;即从节点 i …

Openstack云计算架构及前期服务搭建

openstack介绍 Openstack是一个开源的云计算管理平台项目&#xff0c;由几个主要的组件组合起来完成具体工作&#xff0c;支持几乎所有的云环境&#xff0c;项目目标是提供实施简单、可大规模扩展、丰富、标准统一的云计算管理平台 ----百度百科 Openstack是一个云操作系统&a…

wechat-bot-wechat4u微信机器人

欢迎使用&#x1f44f;&#x1f3fb;wechat-bot-wechat4u &#x1f306; 简介 wechat-bot-wechat4u&#xff0c;基于wechat4u进行开发&#xff0c;接收微信账号消息并提供自动回复、记录存储、消息推送、消息转发等功能&#xff0c;可通过自定义实现各种功能&#xff0c;诸如…

Acwing数学与简单DP(二)

摘花生 原题链接&#xff1a;https://www.acwing.com/problem/content/1017/ 最后一步&#xff0c;有两种可能&#xff1a; 从上面走从下面走 也就是max(dp[i-1][j],dp[i][j-1])&#xff0c;再加上最后一个位置的值。 #include"bits/stdc.h"using namespace std;i…

Rocky Linux 运维工具 ls

一、ls 的简介 ​​ls​ 用于列出当前目录下的文件和目录&#xff0c;以及它们的属性信息。通过 ​ls​命令可以查看文件名、文件大小、创建时间等信息&#xff0c;并方便用户浏览和管理文件。 二、ls 的参数说明 序号参数描述1-a显示所有文件&#xff0c;包括以 ​.​开头的…

golang学习7,glang的web的restful接口结构体传参

接口&#xff1a; //POST请求 返回json 接口传参json r.POST("/postJson", controller.PostUserInfo) 1.定义结构体 //定义结构体 type Search struct {Id intName string }2.结构体传参 //结构体传参 func PostUserInfo(c *gin.Context) {search : &Searc…

pytorch安装GPU版本 (Cuda12.1)教程

使用本教程前&#xff0c;默认您已经安装并配置好了python3以上版本 1. 去官网下载匹配的Cuda Cuda下载地址 当前最高版本的Cuda是12.1 我安装的就是这个版本 小提示&#xff1a;自定义安装可以只选择安装Cuda Runtime。Nvidia全家桶不必全部安装。把全家桶全部安装完直接系统…

VPX基于全国产飞腾FT-2000+/64核+复旦微FPGA的计算刀片

6U VPX计算板 产品简介 产品特点 飞腾计算平台&#xff0c;国产化率100% VPX-MPU6902是一款基于飞腾FT-2000/64核的计算刀片&#xff0c;主频2.2GHz&#xff0c;负责业务数据流的管控和调度。搭配自带独立显示芯片的飞腾X100芯片&#xff0c;可用于于各类终端及服务器类应用场…

【毕业设计推荐】基于MATLAB的水果分级系统设计与实现

一、课题介绍 现在商业行为中&#xff0c;在水果出厂前都需要进行质量检测&#xff0c;需要将不同等级的水果进行分级包装&#xff0c;以保证商业利益最大化。可是传统方法都是依靠人工进行检测&#xff0c;效率低下&#xff0c;主观成分大&#xff0c;并不能很好客观地评价出货…

【笔记】深度学习入门:基于Python的理论与实现(五)

卷积神经网络 卷积神经网络(Convolutional Neural Network&#xff0c;CNN) 整体结构 CNN 中新出现了卷积层(Convolution 层)和池化层(Pooling 层)&#xff0c;之前介绍的神经网络中&#xff0c;相邻层的所有神经元之间都有连接&#xff0c;这称为全 连接(fully-connected) …

Linux Seccomp 简介

文章目录 一、简介二、架构三、Original/Strict Mode四、Seccomp-bpf五、seccomp系统调用六、Linux Capabilities and Seccomp6.1 Linux Capabilities6.2 Linux Seccomp 参考资料 一、简介 Seccomp&#xff08;secure computing&#xff09;是Linux内核中的一项计算机安全功能…

4. client-go 编程式交互

Kubernetes 系统使用 client-go 作为 Go 语言的官方编程式交互客户端库&#xff0c;提供对 Kubernetes API Server 服务的交互访问。Kubernetes 的源码中已经集成了 client-go 的源码&#xff0c;无须单独下载。client-go 源码路径为 vendor/k8s.io/client-go。 开发者经常使用…

四种经典限流算法讲解

1. 固定窗口限流算法 1.1 什么是固定窗口限流算法 固定窗口限流算法&#xff08;Fixed Window Rate Limiting Algorithm&#xff09;是一种最简单的限流算法&#xff0c;其原理是在固定时间窗口(单位时间)内限制请求的数量。该算法将时间分成固定的窗口&#xff0c;并在每个窗…

【Java】双亲委派机制及其打破的相关知识笔记

目录 类的加载机制 类加载器 双亲委派模型机制 为什么要有双亲委派机制 如何打破双亲委派机制 类的加载机制 类加载过程是由Java虚拟机的类加载子系统完成的&#xff0c;它负责将字节码加载到内存中&#xff0c;并进行链接和初始化操作。类加载过程是Java中非常重要的一部…

六、防御保护---防火墙内容安全篇

六、防御保护---防火墙内容安全篇 一、IAE&#xff08;Intelligent Awareness Engine&#xff09;引擎二、深度检测技术(DFI和DPI&#xff09;2.1 DPI -- 深度包检测技术2.1.1 基于“特征字”的检测技术2.1.2 基于应用网关的检测技术2.1.3 基于行为模式的检测技术 2.2 DFI -- 深…

163邮箱SMTP端口号及服务器地址详细设置?

163邮箱SMTP端口号是什么&#xff1f;163邮件SMTP设置教程&#xff1f; 除了基本的邮箱账号和密码外&#xff0c;还需要了解SMTP服务器地址和端口号&#xff0c;以及相应的设置。这些设置对于确保邮件能够顺利发送至关重要。下面&#xff0c;蜂邮EDM将详细介绍163邮箱SMTP端口…