热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Kotlin协程(一)

文章目录一、前言二、线程与协程的区别线程需要大量的资源。不可预测的行为竞态条件三、协程GlobalScope.launchrunBlocking简述async()何时将函数标记为s


文章目录

    • 一、前言
    • 二、线程与协程的区别
      • 线程需要大量的资源。
    • 不可预测的行为
      • 竞态条件
    • 三、协程
      • GlobalScope.launch
    • runBlocking 简述
      • async()
    • 何时将函数标记为 suspend
    • 四、线程的切换
    • 五、参考链接


一、前言

​ 在kotlin中协程用法是比较广泛的,协程也是比较复杂的,本文只对其做个简单的记录,后面再进行详细描述


二、线程与协程的区别

​ 由于有些耗时操作需要等待,因此常常放在子线程中去操作,用来提升用户体验。但是线程存在一些以下问题(这些问题其实可以解决,只是比较麻烦),所以kotlin建议使用协程来处理异步任务


协程能够处理多任务,但比直接使用线程更为抽象。协程的一项重要功能是能够存储状态,以便协程可以暂停和恢复。协程可以执行,也可以不执行。

借助状态(由“连续性”表示),部分代码可以在需要移交控制权或需要等待其他协程完成后才能恢复时发出信号。此流程称为“协作式多任务处理”。Kotlin 的协程实现增加了一些协助处理多任务的功能。除了连续性以外,创建协程还涉及到作用于 CoroutineScope 内的 Job(具有生命周期的可取消工作单元)的内容。CoroutineScope 表示以递归方式对其子级以及这些子级的子级强制执行取消和其他规则的一种上下文。Dispatcher 会管理协程将使用哪个后备线程来执行任务,从而使开发者无需管理使用新线程的时间和位置。


概念解释
Job表示可取消的工作单元,例如使用 launch() 函数创建的工作单元。
CoroutineScope用于创建新协程的函数,例如 launch()async()CoroutineScope 进行扩展。
Dispatcher调度程序确定协程将使用的线程。Main 调度程序将始终在主线程上运行协程,而 DefaultIOUnconfined 等调度程序则会使用其他线程。

三、协程


  • GlobalScope.launch

    只要应用在运行,GlobalScope 便允许其中的任何协程运行。鉴于我们讨论的关于主线程的原因,我们不建议在示例代码之外使用这种方法。在应用中使用协程时,我们会使用其他作用域。

    launch() 函数会根据括起来的代码(封装在可取消作业对象中)创建协程。launch() 用于无需在协程范围之外返回值的情况。

    我们来看一下 launch() 的完整签名,以了解协程中的下一个重要概念。

    fun CoroutineScope.launch {context: CoroutineContext = EmptyCoroutineContext,start: CoroutineStart = CoroutineStart.DEFAULT,block: suspend CoroutineScope.() -> Unit
    }

    在后台,您传递给 launch 函数的代码块会使用 suspend 关键字进行标记。Suspend 表示代码块或函数可以暂停或恢复。

  • runBlocking 简述

    该函数会启动新协程并在新协程完成之前阻塞当前线程。它主要用于在主要函数和测试中的阻塞代码和非阻塞代码之间架起桥梁。该函数在典型的 Android 代码中并不常用。

    import kotlinx.coroutines.*
    import java.time.LocalDateTime
    import java.time.format.DateTimeFormatterval formatter = DateTimeFormatter.ISO_LOCAL_TIME
    val time = { formatter.format(LocalDateTime.now()) }suspend fun getValue(): Double {println("entering getValue() at ${time()}")delay(3000)println("leaving getValue() at ${time()}")return Math.random()
    }fun main() {runBlocking {val num1 = getValue()val num2 = getValue()println("result of num1 + num2 is ${num1 + num2}")}
    }

  • async()

    上述的函数会字啊第一个getValue()执行完毕再执行另外一个。而使用async()修饰的函数则会异步操作,在第一个函数等待期间执行其它函数

    fun main() {runBlocking {val num1 = async { getValue() }val num2 = async { getValue() }println("result of num1 + num2 is ${num1.await() + num2.await()}")}
    }

    Fun CoroutineScope.async() {context: CoroutineContext = EmptyCoroutineContext,start: CoroutineStart = CoroutineStart.DEFAULT,block: suspend CoroutineScope.() -> T
    }: Deferred<T>

    async() 函数会返回 Deferred 类型的值。Deferred 是一个可取消的 Job&#xff0c;可以存储对未来值的引用。使用 Deferred&#xff0c;您仍然可以调用函数&#xff0c;就像它会立即返回一个值一样 - Deferred 只充当占位符&#xff0c;因为您无法确定异步任务将何时返回。Deferred&#xff08;在其他语言中也称为 promise 或 future&#xff09;能够保证稍后会向此对象返回一个值。另一方面&#xff0c;异步任务默认不会阻塞或等待执行。若要启动异步任务&#xff0c;当前的代码行需要等待 Deferred 的输出&#xff0c;您可以对其调用 await()。它将会返回原始值

  • 何时将函数标记为 suspend

    在前面的示例中&#xff0c;您可能已经注意到 getValue() 函数也使用 suspend 关键字进行了定义。原因在于它会调用 delay()&#xff0c;这也是一个 suspend 函数。只要一个函数调用另一个 suspend 函数&#xff0c;那它也应是 suspend 函数。

    如果这样的话&#xff0c;为什么我们示例中的 main() 函数不能用 suspend 进行标记呢&#xff1f;毕竟&#xff0c;它也调用 getValue()

    不一定。getValue() 实际上是在传递给 runBlocking() 的函数中调用的&#xff0c;这是一个 suspend 函数&#xff0c;类似于传递给 launch()async() 的函数。而 getValue() 不是在 main() 本身中调用的&#xff0c;runBlocking() 也不是 suspend 函数&#xff0c;因此 main() 没有用 suspend 标记。如果函数未调用 suspend 函数&#xff0c;它本身就无需是 suspend 函数。


四、线程的切换

协程是在线程上创建的&#xff0c;因此如果协程创建在主线程上面的话&#xff0c;执行网络请求的话依然会使程序出现错误。所以类似于下文的代码是不正确的

viewModelScope.launch() {val jsonBody &#61; "{ username: \"$username\", token: \"$token\"}"loginRepository.makeLoginRequest(jsonBody)//执行网络请求
}

因此需要使用Dispatcher调度程序将线程切换到子线程&#xff0c;如下:

viewModelScope.launch(Dispatchers.IO) {//如果不写这个默认就是挂在主线程上面&#xff0c;程序还是会出现异常val jsonBody &#61; "{ username: \"$username\", token: \"$token\"}"loginRepository.makeLoginRequest(jsonBody)
}

或者withContext(Dispatchers.IO)使用&#96;&#96;将makeLoginRequest改为子线程&#xff0c;如下:

withContext(Dispatchers.IO) {// Blocking network request code}

withContext(Dispatchers.IO)中的代码执行完毕后&#xff0c;程序自动回到原先的线程而无需切换。同时使用withContext(Dispatchers.IO)的函数需要使用挂起suspend来进行修饰。

完整代码如下&#xff1a;

sealed class Result<out R> {data class Success<out T>(val data: T) : Result<T>()data class Error(val exception: Exception) : Result<Nothing>()
}
private const val loginUrl &#61; "https://www.baidu.com/"
class LoginRepository(private val responseParser: LoginResponseParser) {// Function that makes the network request, blocking the current threadsuspend fun makeLoginRequest(jsonBody: String): Result<LoginResponse> {val url &#61; URL(loginUrl)(url.openConnection() as? HttpURLConnection)?.run {requestMethod &#61; "POST"setRequestProperty("Content-Type", "application/json; utf-8")setRequestProperty("Accept", "application/json")doOutput &#61; trueoutputStream.write(jsonBody.toByteArray())return Result.Success(responseParser.parse(inputStream))}return Result.Error(Exception("Cannot open HttpURLConnection"))}
}

五、参考链接


  1. 协程简介

    https://developer.android.google.cn/codelabs/basic-android-kotlin-training-introduction-coroutines

  2. Kotlin协程

    https://developer.android.google.cn/kotlin/coroutines

  3. 协程

    https://kotlinlang.org/docs/coroutines-guide.html


推荐阅读
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 本文介绍了在 Java 编程中遇到的一个常见错误:对象无法转换为 long 类型,并提供了详细的解决方案。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 实验九:使用SharedPreferences存储简单数据
    本实验旨在帮助学生理解和掌握使用SharedPreferences存储和读取简单数据的方法,包括程序参数和用户选项。 ... [详细]
  • 深入解析 Lifecycle 的实现原理
    本文将详细介绍 Android Jetpack 中 Lifecycle 组件的实现原理,帮助开发者更好地理解和使用 Lifecycle,避免常见的内存泄漏问题。 ... [详细]
  • 解决Bootstrap DataTable Ajax请求重复问题
    在最近的一个项目中,我们使用了JQuery DataTable进行数据展示,虽然使用起来非常方便,但在测试过程中发现了一个问题:当查询条件改变时,有时查询结果的数据不正确。通过FireBug调试发现,点击搜索按钮时,会发送两次Ajax请求,一次是原条件的请求,一次是新条件的请求。 ... [详细]
  • 第二十五天接口、多态
    1.java是面向对象的语言。设计模式:接口接口类是从java里衍生出来的,不是python原生支持的主要用于继承里多继承抽象类是python原生支持的主要用于继承里的单继承但是接 ... [详细]
  • 装饰者模式(Decorator):一种灵活的对象结构设计模式
    装饰者模式(Decorator)是一种灵活的对象结构设计模式,旨在为单个对象动态地添加功能,而无需修改原有类的结构。通过封装对象并提供额外的行为,装饰者模式比传统的继承方式更加灵活和可扩展。例如,可以在运行时为特定对象添加边框或滚动条等特性,而不会影响其他对象。这种模式特别适用于需要在不同情况下动态组合功能的场景。 ... [详细]
  • 在《Cocos2d-x学习笔记:基础概念解析与内存管理机制深入探讨》中,详细介绍了Cocos2d-x的基础概念,并深入分析了其内存管理机制。特别是针对Boost库引入的智能指针管理方法进行了详细的讲解,例如在处理鱼的运动过程中,可以通过编写自定义函数来动态计算角度变化,利用CallFunc回调机制实现高效的游戏逻辑控制。此外,文章还探讨了如何通过智能指针优化资源管理和避免内存泄漏,为开发者提供了实用的编程技巧和最佳实践。 ... [详细]
  • 您的数据库配置是否安全?DBSAT工具助您一臂之力!
    本文探讨了Oracle提供的免费工具DBSAT,该工具能够有效协助用户检测和优化数据库配置的安全性。通过全面的分析和报告,DBSAT帮助用户识别潜在的安全漏洞,并提供针对性的改进建议,确保数据库系统的稳定性和安全性。 ... [详细]
  • 卓盟科技:动态资源加载技术的兼容性优化与升级 | Android 开发者案例分享
    随着游戏内容日益复杂,资源加载过程已不仅仅是简单的进度显示,而是连接玩家与开发者的桥梁。玩家对快速加载的需求越来越高,这意味着开发者需要不断优化和提升动态资源加载技术的兼容性和性能。卓盟科技通过一系列的技术创新,不仅提高了加载速度,还确保了不同设备和系统的兼容性,为用户提供更加流畅的游戏体验。 ... [详细]
  • POJ 2482 星空中的星星:利用线段树与扫描线算法解决
    在《POJ 2482 星空中的星星》问题中,通过运用线段树和扫描线算法,可以高效地解决星星在窗口内的计数问题。该方法不仅能够快速处理大规模数据,还能确保时间复杂度的最优性,适用于各种复杂的星空模拟场景。 ... [详细]
  • 如何撰写适应变化的高效代码:策略与实践
    编写高质量且适应变化的代码是每位程序员的追求。优质代码的关键在于其可维护性和可扩展性。本文将从面向对象编程的角度出发,探讨实现这一目标的具体策略与实践方法,帮助开发者提升代码效率和灵活性。 ... [详细]
  • 短信验证码安全性堪忧,多因素认证或成未来主流
    短信验证码安全性堪忧,多因素认证或成未来主流 ... [详细]
  • 如何利用Java 5 Executor框架高效构建和管理线程池
    Java 5 引入了 Executor 框架,为开发人员提供了一种高效管理和构建线程池的方法。该框架通过将任务提交与任务执行分离,简化了多线程编程的复杂性。利用 Executor 框架,开发人员可以更灵活地控制线程的创建、分配和管理,从而提高服务器端应用的性能和响应能力。此外,该框架还提供了多种线程池实现,如固定线程池、缓存线程池和单线程池,以适应不同的应用场景和需求。 ... [详细]
author-avatar
蓝星星空
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有