plsql develop怎么停止job_Kotlin协程实现原理:CoroutineScopeamp;Job
今天我们来聊聊Kotlin
的协程Coroutine
。
如果你还没有接触过协程,推荐你先阅读这篇入门级文章What? 你还不知道Kotlin Coroutine?
如果你已经接触过协程,但对协程的原理存在疑惑,那么在阅读本篇文章之前推荐你先阅读下面的文章,这样能让你更全面更顺畅的理解这篇文章。
Kotlin协程实现原理:Suspend&CoroutineContext
如果你已经接触过协程,相信你都有过以下几个疑问:
- 协程到底是个什么东西?
- 协程的
suspend
有什么作用,工作原理是怎样的? - 协程中的一些关键名称(例如:
Job
、Coroutine
、Dispatcher
、CoroutineContext
与CoroutineScope
)它们之间到底是怎么样的关系? - 协程的所谓非阻塞式挂起与恢复又是什么?
- 协程的内部实现原理是怎么样的?
- ...
接下来的一些文章试着来分析一下这些疑问,也欢迎大家一起加入来讨论。
CoroutineScope
CoroutineScope
是什么?如果你觉得陌生,那么GlobalScope
、lifecycleScope
与viewModelScope
相信就很熟悉了吧(当然这个是针对于Android
开发者)。它们都实现了CoroutineScope
接口。
public interface CoroutineScope {/*** The context of this scope.* Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.* Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.** By convention, should contain an instance of a [job][Job] to enforce structured concurrency.*/public val coroutineContext: CoroutineContext
}
CoroutineScope
中只包含一个待实现的变量CoroutineContext
,至于CoroutineContext
之前的文章已经分析了它的内部结构,这里就不再累赘了。
通过它的结构,我们可以认为它是提供CoroutineContext
的容器,保证CoroutineContext
能在整个协程运行中传递下去,约束CoroutineContext
的作用边界。
例如,在Android
中使用协程来请求数据,当接口还没有请求完成时Activity
就已经退出了,这时如果不停止正在运行的协程将会造成不可预期的后果。所以在Activity
中我们都推荐使用lifecycleScope
来启动协程,lifecycleScope
可以让协程具有与Activity
一样的生命周期意识。
下面是lifecycleScope
源码:
val LifecycleOwner.lifecycleScope: LifecycleCoroutineScopeget() = lifecycle.coroutineScopeval Lifecycle.coroutineScope: LifecycleCoroutineScopeget() {while (true) {val existing = mInternalScopeRef.get() as LifecycleCoroutineScopeImpl?if (existing != null) {return existing}val newScope = LifecycleCoroutineScopeImpl(this,SupervisorJob() + Dispatchers.Main.immediate)if (mInternalScopeRef.compareAndSet(null, newScope)) {newScope.register()return newScope}}}
它创建了一个LifecycleCoroutineScopeImpl
实例,它实现了CoroutineScope
接口,同时传入SupervisorJob() + Dispatchers.Main
作为它的CoroutineContext
。
我们再来看它的register()
方法
internal class LifecycleCoroutineScopeImpl(override val lifecycle: Lifecycle,override val coroutineContext: CoroutineContext
) : LifecycleCoroutineScope(), LifecycleEventObserver {init {// in case we are initialized on a non-main thread, make a best effort check before// we return the scope. This is not sync but if developer is launching on a non-main// dispatcher, they cannot be 100% sure anyways.if (lifecycle.currentState == Lifecycle.State.DESTROYED) {coroutineContext.cancel()}}fun register() {// TODO use Main.Immediate once it is graduated out of experimental.launch(Dispatchers.Main) {if (lifecycle.currentState >= Lifecycle.State.INITIALIZED) {lifecycle.addObserver(this@LifecycleCoroutineScopeImpl)} else {coroutineContext.cancel()}}}override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {if (lifecycle.currentState <= Lifecycle.State.DESTROYED) {lifecycle.removeObserver(this)coroutineContext.cancel()}}
在register
方法中通过经典的launch
来创建一个协程,而launch
使用到的CoroutineContext
就是CoroutineSope
中的CoroutineContext
。然后在协程中结合Jetpack
的Lifecycle
特性来监听Activiyt
的生命周期。
如果对
Lifecycle
的使用与特性还不是很了解的,推荐阅读这篇入门级文章Android Architecture Components Part3:Lifecycle
意思就是说在Activity
销毁的时候会调用下面的方法取消协程的运行。
coroutineContext.cancel()
这里就使用到了CoroutineContext
,经过上篇文章的分析我们很容易知道CoroutineContext
自身是没有cancel
方法的,所以这个cancel
方法是CoroutineContext
的扩展方法。
public fun CoroutineContext.cancel(): Unit {this[Job]?.cancel()
}
所以真正的逻辑是从CoroutineContex
集合中取出Key
为Job
的实例,这个对应的就是上面创建LifecycleCoroutineScopeImpl
实例时传入的SupervisorJob
,它是CoroutineContext
的其中一个子类。
这时再来看lifecycleScope
相关的一些方法
lifecycleScope.launchWhenCreated { }
lifecycleScope.launchWhenStarted { }
lifecycleScope.launchWhenResumed { }
这些方法的内部逻辑就很明显了,也就是通过Lifecycle
来追踪Activity
的生命周期,从而约束协程运行的时机。
我们也可以不使用lifecycleScope
,自己实现一个CoroutineScope
,让它在Activity
达到同样的效果。
class MyActivity : AppCompatActivity(), CoroutineScope {lateinit var job: Joboverride val coroutineContext: CoroutineContextget() = Dispatchers.Main + joboverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)job = Job()}override fun onDestroy() {super.onDestroy()job.cancel() // Cancel job on activity destroy. After destroy all children jobs will be cancelled automatically}/** Note how coroutine builders are scoped: if activity is destroyed or any of the launched coroutines* in this method throws an exception, then all nested coroutines are cancelled.*/fun loadDataFromUI() = launch { // <- extension on current activity, launched in the main threadval ioData = async(Dispatchers.IO) { // <- extension on launch scope, launched in IO dispatcher// blocking I/O operation}// do something else concurrently with I/Oval data = ioData.await() // wait for result of I/Odraw(data) // can draw in the main thread}}
上面的实现也能够保证当前Activiyt
中的协程在Activity
销毁的时候终止协程的运行。
到这里CoroutineScope
的作用就呼之欲出了,它就是用来约束协程的边界,能够很好的提供对应的协程取消功能,保证协程的运行范围。
当然这又引申出另外一个话题
Job
是什么?
Job
基本上每启动一个协程就会产生对应的Job
,例如
lifecycleScope.launch {
}
launch
返回的就是一个Job
,它可以用来管理协程,一个Job
中可以关联多个子Job
,同时它也提供了通过外部传入parent
的实现
public fun Job(parent: Job? = null): Job = JobImpl(parent)
这个很好理解,当传入parent
时,此时的Job
将会作为parent
的子Job
。
既然Job
是来管理协程的,那么它提供了六种状态来表示协程的运行状态。
New
: 创建Active
: 运行Completing
: 已经完成等待自身的子协程Completed
: 完成Cancelling
: 正在进行取消或者失败Cancelled
: 取消或失败
这六种状态Job
对外暴露了三种状态,它们随时可以通过Job
来获取
public val isActive: Boolean
public val isCompleted: Boolean
public val isCancelled: Boolean
所以如果你需要自己来手动管理协程,可以通过下面的方式来判断当前协程是否在运行。
while (job.isActive) {
// 协程运行中
}
一般来说,协程创建的时候就处在Active
状态,但也有特例。
例如我们通过launch
启动协程的时候可以传递一个start
参数
public fun CoroutineScope.launch(context: CoroutineContext = EmptyCoroutineContext,start: CoroutineStart = CoroutineStart.DEFAULT,block: suspend CoroutineScope.() -> Unit
): Job {...
}
如果这个start
传递的是CoroutineStart.LAZY
,那么它将处于New
状态。可以通过调用start
或者join
来唤起协程进入Active
状态。
下面我们来看一张简图,就能很清晰的了解Job
中的六个状态间的转化过程。
wait children+-----+ start +--------+ complete +-------------+ finish +-----------+| New | -----> | Active | ---------> | Completing | -------> | Completed |+-----+ +--------+ +-------------+ +-----------+| cancel / fail || +----------------+| |V V+------------+ finish +-----------+| Cancelling | --------------------------------> | Cancelled |+------------+ +-----------+
上面已经提及到一个Job
可以有多个子Job
,所以一个Job
的完成都必须等待它内部所有的子Job
完成;对应的cancel
也是一样的。
默认情况下,如果内部的子Job
发生异常,那么它对应的parent Job
与它相关连的其它子Job
都将取消运行。俗称连锁反应。
我们也可以改变这种默认机制,Kotlin
提供了SupervisorJob
来改变这种机制。这种情况还是很常见的,例如用协程请求两个接口,但并不想因为其中一个接口失败导致另外的接口也不请求,这时就可以使用SupervisorJob
来改变协程的这种默认机制。
使用很简单,在我们创建CoroutineContext
的时候加入SupervisorJob
即可。例如在上面提到过的lifecycleScope
,内部就使用到了SupervisorJob
val newScope = LifecycleCoroutineScopeImpl(this,SupervisorJob() + Dispatchers.Main
)
你也可以尝试运行下面的这个例子,然后将它的SupervisorJob
替换成别的CoroutineContext
再来看下效果。
fun main() = runBlocking {val supervisor = SupervisorJob()with(CoroutineScope(coroutineContext + supervisor)) {// 启动第一个子作业——这个示例将会忽略它的异常(不要在实践中这么做!)val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {println("The first child is failing")throw AssertionError("The first child is cancelled")}// 启动第二个子作业val secondChild = launch {firstChild.join()// 取消了第一个子作业且没有传播给第二个子作业println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")try {delay(Long.MAX_VALUE)} finally {// 但是取消了监督的传播println("The second child is cancelled because the supervisor was cancelled")}}// 等待直到第一个子作业失败且执行完成firstChild.join()println("Cancelling the supervisor")supervisor.cancel()secondChild.join()}
}
如果有些任务你并不想被手动取消,可以使用NonCancellable
作为任务的CoroutineContext
如果需要Job
获取协程的返回结果,可以通过Deferred
来实现,它是Job
的一个子类,所以也拥有Job
所用功能。同时额外提供await
方法来等待协程结果的返回。
Deferred
可以通过CoroutineScope.async
创建。
最后我们再来介绍下Job
的几个方法,start
与cancel
就不多说了,分别是启动与取消。
invokeOnCompletion
这个方法是Job
的回调通知,当Job
执行完后会调用这个方法
public fun invokeOnCompletion(handler: CompletionHandler): DisposableHandlepublic typealias CompletionHandler = (cause: Throwable?) -> Unit
这个cause
有三种情况分别为:
is null
: 协程正常执行完毕is CancellationException
: 协程正常取消,并非异常导致的取消Otherwise
: 协程发生异常
同时它的返回值DisposableHandle
可以用来取消回调的监听。
join
public suspend fun join()
注意这是一个suspend
函数,所以它只能在suspend
或者coroutine
中进行调用。
它的作用是暂停当前运行的协程任务,立刻执行自身Job
的协程任务,直到自身执行完毕之后才恢复之前的协程任务继续执行。
本篇文章主要介绍了CoroutineScope
的作用与Job
的相关状态演化与运用。希望对学习协程的伙伴们能够有所帮助,敬请期待后续的协程分析。
项目
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: 每日一算法,由浅入深,欢迎加入一起共勉。
plsql develop怎么停止job_Kotlin协程实现原理:CoroutineScopeamp;Job相关推荐
- Unity 的协程的原理
Unity是一款非常强大的游戏引擎,它支持多种编程语言,其中最常用的语言是C#.在Unity中,协程是一种非常强大的功能,它可以让我们在游戏中实现各种各样的效果.本文将详细介绍Unity协程的原理,并 ...
- Tornado 异步协程coroutine原理
协程定义: 协程,又称微线程,纤程.英文名Coroutine. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕 ...
- unity update 协程_Unity 协程的原理
Unity 协程的原理 发布时间:2019-06-13 18:45, 浏览次数:1118 , 标签: Unity 协程不是多线程,协程还是在主线程里面(注:在Unity中非主线程是不可以访问Unity ...
- python协程异步原理_简单介绍Python的Tornado框架中的协程异步实现原理
Tornado 4.0 已经发布了很长一段时间了, 新版本广泛的应用了协程(Future)特性. 我们目前已经将 Tornado 升级到最新版本, 而且也大量的使用协程特性. 很长时间没有更新博客, ...
- pdf 深入理解kotlin协程_Kotlin协程实现原理:挂起与恢复
今天我们来聊聊Kotlin的协程Coroutine. 如果你还没有接触过协程,推荐你先阅读这篇入门级文章What? 你还不知道Kotlin Coroutine? 如果你已经接触过协程,但对协程的原理存 ...
- Kotlin协程实现原理
前言 本篇解析Kotlin/JVM中的协程的实现原理. 初看suspend关键字 下面的例子模拟一个网络请求: class Temp {suspend fun fetchData(argument: ...
- 协程的原理及协程在高并发服务中的应用
协程的原理 协程(coroutine)跟具有操作系统概念的线程不一样,实际上协程就是类函数一样的程序组件,你可以在一个线程里面轻松创建数十万个协程,就像数十万次函数调用一样.只不过函数只有一个调用入口 ...
- 协程的原理和应用,C++现实协程
协程的原理 协程(coroutine)跟具有操作系统概念的线程不一样,实际上协程就是类函数一样的程序组件,你可以在一个线程里面轻松创建数十万个协程,就像数十万次函数调用一样.只不过函数只有一个调用入口 ...
- 在C语言中实现协程库(一)----------协程切换原理详解
从这篇文章开始,我将一点一点详细介绍如何在c语言中实现协程库.并对其中涉及到的技术进行详细的解释. 感兴趣的小伙伴欢迎一起参与 代码地址 协程切换原理 使用glibc中<ucontext.h&g ...
最新文章
- “睡眠猴子”团队项目及成员介绍
- execv shell_shell的exec命令
- Oralce中日期格式
- android数据库给单选赋值,如何使用android studio将单选按钮的值保存到mysql数据库?...
- 大数据-MapReduce计算框架
- PHP面向对象中new self( )和 new static( ) 的区别
- 黑马程序员C++学习笔记(第二阶段核心:面向对象)(二)
- 20.大型网站典型故障分析
- Web前端开发工程师基本要求
- ActivityMQ应用详解
- 产品读书《自控力:斯坦福大学最受欢迎的心理学课程》
- 外贸最全出口流程,外贸必看基础知识
- 深圳农商行智能柜台项目建设案例
- CSDN博客新增「评论置顶」、「定时发布」功能,翘首期盼的PC版「每日一练」上线!【第14期*2021.9.22】
- P3324 [SDOI2015]星际战争(洛谷刷题记录)
- php自学多久可以上岗,自学php多久能找工作,PHP要自学多久才能找到工作
- 什么样的程序员才算得上优秀,把导师曾对我说的话送给大家(比代码更重要的事)
- [转]孙悟空无姓无名的时候,阎王生死簿是怎么写的呢?
- JS高级程序设计读书笔记(第五章 引用变量)
- 关于手持设备PDA的开发
热门文章
- mysql数据库多级分类汇总_sql多级分类汇总实现介绍
- gradle项目 避免每次下载gradle文件/解决依赖下载慢的问题
- 从“零”开始的存储历程到“零”外置的云存储
- C语言----项目构建Make,Automake,CMake
- Javascript模块化编程(三):require.js的用法
- 3最短路的几种解法 ------例题 最短路
- Eclipse快捷键详细解析
- 字符编码的知识(二)
- 面对复杂业务架构,阿里架构师是如何做的?(第一期)
- 不敢相信,居然用Java写了个“天天酷跑”!