android 协程,关于android:Kotlin协程实现原理SuspendCoroutineContext

明天咱们来聊聊Kotlin的协程Coroutine。

如果你还没有接触过协程,举荐你先浏览这篇入门级文章What? 你还不晓得Kotlin Coroutine?

如果你曾经接触过协程,置信你都有过以下几个疑难:

协程到底是个什么货色?

协程的suspend有什么作用,工作原理是怎么的?

协程中的一些要害名称(例如:Job、Coroutine、Dispatcher、CoroutineContext与CoroutineScope)它们之间到底是怎么样的关系?

协程的所谓非阻塞式挂起与复原又是什么?

协程的外部实现原理是怎么样的?

接下来的一些文章试着来剖析一下这些疑难,也欢送大家一起退出来探讨。

协程是什么

这个疑难很简略,只有你不是野路子接触协程的,都应该可能晓得。因为官网文档中曾经明确给出了定义。

上面来看下官网的原话(也是这篇文章最具备底气的一段话)。

协程是一种并发设计模式,您能够在 Android 平台上应用它来简化异步执行的代码。

敲黑板划重点:协程是一种并发的设计模式。

所以并不是一些人所说的什么线程的另一种体现。尽管协程的外部也应用到了线程。但它更大的作用是它的设计思维。将咱们传统的Callback回调形式进行打消。将异步编程趋近于同步对齐。

解释了这么多,最初咱们还是间接点,来看下它的长处

轻量:您能够在单个线程上运行多个协程,因为协程反对挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节俭内存,且反对多个并行操作。

内存泄露更少:应用结构化并发机制在一个作用域内执行多个操作。

内置勾销反对:勾销性能会主动通过正在运行的协程层次结构流传。

Jetpack集成:许多 Jetpack 库都蕴含提供全面协程反对的扩大。某些库还提供本人的协程作用域,可供您用于结构化并发。

suspend

suspend是协程的关键字,每一个被suspend润饰的办法都必须在另一个suspend函数或者Coroutine协程程序中进行调用。

第一次看到这个定义不晓得你们是否有疑难,反正小憩我是很纳闷,为什么suspend润饰的办法须要有这个限度呢?不加为什么就不能够,它的作用到底是什么?

当然,如果你有关注我之前的文章,应该就会有所理解,因为在重温Retrofit源码,笑看协程实现这篇文章中我曾经有简略的提及。

这里波及到一种机制俗称CPS(Continuation-Passing-Style)。每一个suspend润饰的办法或者lambda表达式都会在代码调用的时候为其额定增加Continuation类型的参数。

@GET("/v2/news")

suspend fun newsGet(@QueryMap params: Map): NewsResponse

下面这段代码通过CPS转换之后真正的面目是这样的

@GET("/v2/news")

fun newsGet(@QueryMap params: Map, c: Continuation): Any?

通过转换之后,原有的返回类型NewsResponse被增加到新增的Continutation参数中,同时返回了Any?类型。这里可能会有所疑难?返回类型都变了,后果不就出错了吗?

其实不是,Any?在Kotlin中比拟非凡,它能够代表任意类型。

当suspend函数被协程挂起时,它会返回一个非凡的标识COROUTINE_SUSPENDED,而它实质就是一个Any;当协程不挂起进行执行时,它将返回执行的后果或者引发的异样。这样为了让这两种状况的返回都反对,所以应用了Kotlin独有的Any?类型。

返回值搞明确了,当初来说说这个Continutation参数。

首先来看下Continutation的源码

public interface Continuation {

/**

* The context of the coroutine that corresponds to this continuation.

*/

public val context: CoroutineContext

/**

* Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the

* return value of the last suspension point.

*/

public fun resumeWith(result: Result)

}

context是协程的上下文,它更多时候是CombinedContext类型,相似于协程的汇合,这个后续会详情阐明。

resumeWith是用来唤醒挂起的协程。后面曾经说过协程在执行的过程中,为了避免阻塞应用了挂起的个性,一旦协程外部的逻辑执行结束之后,就是通过该办法来唤起协程。让它在之前挂起的地位继续执行上来。

所以每一个被suspend润饰的函数都会获取下层的Continutation,并将其作为参数传递给本人。既然是从下层传递过去的,那么Continutation是由谁创立的呢?

其实也不难猜到,Continutation就是与协程创立的时候一起被创立的。

GlobalScope.launch {

}

launch的时候就曾经创立了Continutation对象,并且启动了协程。所以在它外面进行挂起的协程传递的参数都是这个对象。

简略的了解就是协程应用resumeWith替换传统的callback,每一个协程程序的创立都会随同Continutation的存在,同时协程创立的时候都会主动回调一次Continutation的resumeWith办法,以便让协程开始执行。

CoroutineContext

协程的上下文,它蕴含用户定义的一些数据汇合,这些数据与协程密切相关。它相似于map汇合,能够通过key来获取不同类型的数据。同时CoroutineContext的灵活性很强,如果其须要扭转只需应用以后的CoroutineContext来创立一个新的CoroutineContext即可。

来看下CoroutineContext的定义

public interface CoroutineContext {

/**

* Returns the element with the given [key] from this context or `null`.

*/

public operator fun get(key: Key): E?

/**

* Accumulates entries of this context starting with [initial] value and applying [operation]

* from left to right to current accumulator value and each element of this context.

*/

public fun fold(initial: R, operation: (R, Element) -> R): R

/**

* Returns a context containing elements from this context and elements from other [context].

* The elements from this context with the same key as in the other one are dropped.

*/

public operator fun plus(context: CoroutineContext): CoroutineContext = ...

/**

* Returns a context containing elements from this context, but without an element with

* the specified [key].

*/

public fun minusKey(key: Key): CoroutineContext

/**

* Key for the elements of [CoroutineContext]. [E] is a type of element with this key.

*/

public interface Key

/**

* An element of the [CoroutineContext]. An element of the coroutine context is a singleton context by itself.

*/

public interface Element : CoroutineContext {..}

}

每一个CoroutineContext都有它惟一的一个Key其中的类型是Element,咱们能够通过对应的Key来获取对应的具体对象。说的有点形象咱们间接通过例子来理解。

var context = Job() + Dispatchers.IO + CoroutineName("aa")

LogUtils.d("$context, ${context[CoroutineName]}")

context = context.minusKey(Job)

LogUtils.d("$context")

// 输入

[JobImpl{Active}@158b42c, CoroutineName(aa), LimitingDispatcher@aeb0f27[dispatcher = DefaultDispatcher]], CoroutineName(aa)

[CoroutineName(aa), LimitingDispatcher@aeb0f27[dispatcher = DefaultDispatcher]]

Job、Dispatchers与CoroutineName都实现了Element接口。

如果须要联合不同的CoroutineContext能够间接通过+拼接,实质就是应用了plus办法。

public operator fun plus(context: CoroutineContext): CoroutineContext =

if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation

context.fold(this) { acc, element ->

val removed = acc.minusKey(element.key)

if (removed === EmptyCoroutineContext) element else {

// make sure interceptor is always last in the context (and thus is fast to get when present)

val interceptor = removed[ContinuationInterceptor]

if (interceptor == null) CombinedContext(removed, element) else {

val left = removed.minusKey(ContinuationInterceptor)

if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else

CombinedContext(CombinedContext(left, element), interceptor)

}

}

}

plus的实现逻辑是将两个拼接的CoroutineContext封装到CombinedContext中组成一个拼接链,同时每次都将ContinuationInterceptor增加到拼接链的最尾部.

那么CombinedContext又是什么呢?

internal class CombinedContext(

private val left: CoroutineContext,

private val element: Element

) : CoroutineContext, Serializable {

override fun get(key: Key): E? {

var cur = this

while (true) {

cur.element[key]?.let { return it }

val next = cur.left

if (next is CombinedContext) {

cur = next

} else {

return next[key]

}

}

}

...

}

留神看它的两个参数,咱们间接拿下面的例子来剖析

Job() + Dispatchers.IO

(Job, Dispatchers.IO)

Job对应于left,Dispatchers.IO对应element。如果再拼接一层CoroutineName(aa)就是这样的

((Job, Dispatchers.IO),CoroutineName)

性能相似与链表,但不同的是你可能拿到上一个与你相连的整体内容。与之对应的就是minusKey办法,从汇合中移除对应Key的CoroutineContext实例。

有了这个根底,咱们再看它的get办法就很清晰了。先从element中去取,没有再从之前的left中取。

那么这个Key到底是什么呢?咱们来看下CoroutineName

public data class CoroutineName(

/**

* User-defined coroutine name.

*/

val name: String

) : AbstractCoroutineContextElement(CoroutineName) {

/**

* Key for [CoroutineName] instance in the coroutine context.

*/

public companion object Key : CoroutineContext.Key

/**

* Returns a string representation of the object.

*/

override fun toString(): String = "CoroutineName($name)"

}

很简略它的Key就是CoroutineContext.Key,当然这样还不够,须要持续联合对于的operator get办法,所以咱们再来看下Element的get办法

public override operator fun get(key: Key): E? =

@Suppress("UNCHECKED_CAST")

if (this.key == key) this as E else null

这里应用到了Kotlin的operator操作符重载的个性。那么上面的代码就是等效的。

context.get(CoroutineName)

context[CoroutineName]

所以咱们就能够间接通过相似于Map的形式来获取整个协程中CoroutineContext汇合中对应Key的CoroutineContext实例。

本篇文章次要介绍了suspend的工作原理与CoroutineContext的内部结构。心愿对学习协程的搭档们可能有所帮忙,敬请期待后续的协程剖析。

我的项目

android_startup: 提供一种在利用启动时可能更加简略、高效的形式来初始化组件,优化启动速度。不仅反对Jetpack App Startup的全副性能,还提供额定的同步与异步期待、线程管制与多过程反对等性能。

AwesomeGithub: 基于Github客户端,纯练习我的项目,反对组件化开发,反对账户明码与认证登陆。应用Kotlin语言进行开发,我的项目架构是基于Jetpack&DataBinding的MVVM;我的项目中应用了Arouter、Retrofit、Coroutine、Glide、Dagger与Hilt等风行开源技术。

flutter_github: 基于Flutter的跨平台版本Github客户端,与AwesomeGithub绝对应。

android-api-analysis: 联合具体的Demo来全面解析Android相干的知识点, 帮忙读者可能更快的把握与了解所论述的要点。

daily_algorithm: 每日一算法,由浅入深,欢送退出一起共勉。

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

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

相关文章

清空easyui checkbox选中项

$(#dg).datagrid(unselectAll);转载于:https://www.cnblogs.com/douhuan/p/7116744.html

python 编辑excel需要什么包_Python 中操作EXCEL表格的包

今天,马云爸爸又来贡献金句了,比王健林公公一亿一个小目标还高,“一个月挣一二十个亿很难受!!!”,作为在传统企业主要为电商部门提供数据分析的数据分析师,体验太深刻了。双11前后&a…

用Java处理大文件

最近,我不得不处理一组包含逐笔历史汇率市场数据的文件,并很快意识到使用传统的InputStream都无法将它们读取到内存中,因为每个文件的大小都超过4 GB。 Emacs甚至无法打开它们。 在这种特殊情况下,我可以编写一个简单的bash脚本&…

java IO(一):File类

1.File类简介 File类位于java.io包中。它面向文件层次级别操作、查看文件,而字节流、字符流操作数据时显然比之更底层。 学习File类包括以下几个重点:文件路径、文件分隔符、创建文件(目录)、删除文件(目录)、查看文件内容(输出目录内文件)、判断文件(是…

android listview 开发,android开发之ListView实现

今天又初步学习了一下ListView控件,看看效果如下:LisViewActivity.java源码:package com.jinhoward.UI_listview;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;import android.os.Bundl…

input ios问题 小程序_微信小程序开发常见问题汇总

原标题:微信小程序开发常见问题汇总1、域名必须是https非https的域名不被微信小程序允许。2、input组件placeholder字体颜色卸载placeholder-class里面的color并不生效,需要写在placeholder-style里面就可以了。3、wx.navigateTo无法跳转到带tabbar的页面…

https://github.com/

https://github.com/ qq邮箱 转载于:https://www.cnblogs.com/chang1/p/7133251.html

Less 的用法

1. node.js node.js是一个前端的框架 自带一个包管理工具npm node.js 的安装 官网:http://nodejs.cn/ 在命令行检验是否安装成功 切换到项目目录,初始化了一个package.json文件 安装与卸载jQuery包(例子) 安装 卸载 安装淘宝…

浅谈springboot整合ganymed-ssh2远程访问linux

环境介绍 技术栈 springbootmybatis-plusmysqlganymed-ssh2 软件 版本 mysql 8 IDEA IntelliJ IDEA 2022.2.1 JDK 1.8 Spring Boot 2.7.13 mybatis-plus 3.5.3.2 SSH(远程连接工具)连接原理:ssh服务是一个守护进程(demon),系统后台监听客户…

优化Neo4j Cypher查询

上周,我花了很多时间尝试使用实时系统中的数据来优化大约20个执行失败的Cypher查询(36866ms至155575ms)。 经过一番尝试和错误,以及来自Michael的大量投入,我能够大致确定对查询进行哪些操作才能使它们性能更好-最后&a…

python 多文件知识

对于一个大型的项目,会存在很多个py文件,本文记录与多文件有关的内容。 1. python 如何在一个.py文件中调用另一个.py文件的类 如果是在同一个 module中(也就是同一个py 文件里),直接用就可以如果在不同的module里,例如a.py里有 class A:b.py 里有 class…

android pick file,LFilePicker---文件选择利器,各种样式有它就够了

LFilePicker在 Android 开发中如果需要选择某个文件,可以直接调取系统的文件管理器进行选择,但是无法保证各个厂商的手机界面一致,而且解析Uri 还比较繁琐,如果还需要多选呢?需要文件类型过滤呢?老板说界面…

老笔记整理二:网页小问题汇总

最近有一些小问题。想在这里写出来。一是方便大家排错,再是自己也整理一下。 1。很傻的小问题。。。参数提交方式有一个应该是form而不是from。(英语老师,我对不起你。。。) 2。用超链接传参数,在?后面不能…

在JVM之下–类加载器

在许多开发人员中,类加载器是Java语言的底层,并且经常被忽略。 在ZeroTurnaround上 ,我们的开发人员必须生活,呼吸,饮食,喝酒,并且几乎与类加载器保持亲密关系,才能生产JRebel技术&a…

matplotlib绘制饼状图

源自http://blog.csdn.net/skyli114/article/details/77508430?ticketST-41707-PzNbUDGt6R5KYl3TkWDg-passport.csdn.net pyplot使用plt.pie()来绘制饼图 1 import matplotlib.pyplot as plt 2 labels frogs, hogs, dogs, logs 3 sizes 15, 20, 45, 10 # [15,20,45,10…

自适应宽度元素单行文本省略用法探究

单行文本省略是现代网页设计中非常常用的技术,几乎每个站点都会用到。单行文本省略适用于显示摘要信息的场景,如列表标题、文章摘要等。在响应式开发中,自适应宽度元素单行文本省略容易失效不起作用,对网页开发这造成困扰。因此&a…

P3390 【模板】矩阵快速幂

题目背景 矩阵快速幂 题目描述 给定n*n的矩阵A,求A^k 输入输出格式 输入格式: 第一行,n,k 第2至n1行,每行n个数,第i1行第j个数表示矩阵第i行第j列的元素 输出格式: 输出A^k 共n行,每行n个数&…

c#精彩编程200例百度云_永安市教育局被授予“人工智能编程教育试验区”

11月28日,“第二届人工智能与机器人教育大会青少年人工智能与编程教育主题论坛”在厦门召开。永安市教育局被中国教育发展战略学会人工智能与机器人教育专委会授予“人工智能编程教育试验区”牌匾,巴溪湾小学、西门小学、三中、一中附属学校、实验小学等…

python中+=和=+的区别

本文原创,版权属作者个人所有,如需转载请联系作者本人。Q&微:155122733 -------------------------------------------------------------------------------------------------------- a1 代表在原值上更改 aa1相当于先定义一个变量&…

Spring Data JPA和分页

让我们从支持分页的经典JPA方法开始。 考虑一个简单的域类–一个具有名字,姓氏的“成员”。 为了支持在成员列表上进行分页,JPA方法是支持一种查找器,该查找器将获取第一个结果(firstResult)的偏移量和要检索的结果&am…