简介
-
协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器!)上调度执行,而代码则保持如同顺序执行一样简单。
-
协程是一种并发设计模式,您可以在Android平台上使用它来简化异步执行的代码
协程的启动方式
-
runBlocking:T启动一个新的协程并阻塞调用它的线程,直到里面的代码执行完毕,返回值是泛型T,就是你协程体中最后一行是什么类型,最终返回的是什么类型T就是什么类型。
-
launch:Job 启动一个协程但不会阻塞调用线程,必须要在协程作用域(CoroutineScope)中才能调用,返回值是一个Job。
-
async:Deferred
<T>
启动一个协程但不会阻塞调用线程,必须要在协程作用域(CoroutineScope)中才能调用。以Deferred对象的形式返回协程任务。返回值泛型T同runBlocking类似都是协程体最后一行的类型。
job
Job
我们可以认为他就是一个协程作业是通过CoroutineScope.launch
生成的,同时它运行一个指定的代码块,并在该代码块完成时完成。我们可以通过isActive
、isCompleted
、isCancelled
来获取到Job
的当前状态。Job
的状态如下图所示,摘自官方文档:
协程的生命周期
State | [isActive] | [isCompleted] | [isCancelled] |
---|---|---|---|
New (optional initial state) | false |
false |
false |
Active (default initial state) | true |
false |
false |
Completing (transient state) | true |
false |
false |
Cancelling (transient state) | false |
false |
true |
Cancelled (final state) | false |
true |
true |
Completed (final state) | false |
true |
false |
我们可以通过下图可以大概了解下一个协程作业从创建到完成或者取消
sql
wait children
+-----+ start +--------+ complete +-------------+ finish +-----------+
| New | -----> | Active | ---------> | Completing | -------> | Completed |
+-----+ +--------+ +-------------+ +-----------+
| cancel / fail |
| +----------------+
| |
V V
+------------+ finish +-----------+
| Cancelling | --------------------------------> | Cancelled |
+------------+ +-----------+
Deferred
Deferred
继承自Job
,我们可以把它看做一个带有返回值的Job
,
kotlin
public interface Deferred<out T> : Job {
//返回结果值,或者如果延迟被取消,则抛出相应的异常
public suspend fun await(): T
public val onAwait: SelectClause1<T>
public fun getCompleted(): T
public fun getCompletionExceptionOrNull(): Throwable?
}
什么是作用域
协程作用域(Coroutine Scope
)是协程运行的作用范围。launch
、async
都是CoroutineScope
的扩展函数,CoroutineScope
定义了新启动的协程作用范围,同时会继承了他的coroutineContext
自动传播其所有的 elements
和取消操作。换句话说,如果这个作用域销毁了,那么里面的协程也随之失效。就好比变量的作用域,
协程的基本用法
协程依赖
arduino
// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.0"
// 协程Android支持库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0"
三种启动方式
csharp
runBlocking {
Log.i(TAG,"这是runBlocking方法启动协程")
}
GlobalScope.launch {
Log.i(TAG,"这是launch方法启动协程")
}
GlobalScope.async {
Log.i(TAG,"这是async方法启动协程")
}
挂起函数
suspend
是协程的关键字,表示这是一个挂起函数,每一个被suspend
饰的方法只能在suspend
方法或者在协程中调用
线程调度器
调度器它确定了相关的协程在哪个线程或哪些线程上执行。协程调度器可以将协程限制在一个特定的线程执行,或将它分派到一个线程池,亦或是让它不受限地运行。官方框架预置了4个调度器:
-
Default
:默认调度器,CPU密集型任务调度器,适合处理后台计算。通常处理一些单纯的计算任务,或者执行时间较短任务。比如:Json的解析,数据计算等 -
IO
:IO调度器,,IO密集型任务调度器,适合执行IO相关操作。比如:网络处理,数据库操作,文件操作等 -
Main
:UI调度器, 即在主线程上执行,通常用于UI交互,刷新等 -
Unconfined
:非受限调度器,又或者称为"无所谓"调度器,不要求协程执行在特定线程上。
withContext
withContext
。这个函数可以切换到指定的线程,并在闭包内的逻辑执行结束之后,自动把线程切回去继续执行,并且可以将闭包的执行结果返回
scss
// 方法一,使用 runBlocking 顶层函数
runBlocking (Dispatchers.Main){
val result = withContext(Dispatchers.IO){
Log.i(TAG,"在IO线程中执行")
}
Log.i(TAG,"在主线程中执行")
}
协程的并发与同步
因为协程是采用就是并发的设计模式,这句话的大多数环境下是没有问题。但是这里需要注意,如果某个协程满足以下几点,那它里面的子协程将会是同步执行的:
- 父协程的协程调度器是处于
Dispatchers.Main
情况下启动。 - 同时子协程在不修改协程调度器下的情况下启动。
协程启动模式
CoroutineStart
协程启动模式,是启动协程时需要传入的第二个参数。协程启动有4种:
-
DEFAULT
默认启动模式,我们可以称之为饿汉启动模式,因为协程创建后立即开始调度,虽然是立即调度,单不是立即执行,有可能在执行前被取消。 -
LAZY
懒汉启动模式,启动后并不会有任何调度行为,直到我们需要它执行的时候才会产生调度。也就是说只有我们主动的调用Job
的start
、join
或者await
等函数时才会开始调度。 -
ATOMIC
一样也是在协程创建后立即开始调度,但是它和DEFAULT
模式有一点不一样,通过ATOMIC
模式启动的协程执行到第一个挂起点之前是不响应cancel
取消操作的,ATOMIC
一定要涉及到协程挂起后cancel
取消操作的时候才有意义。 -
UNDISPATCHED
协程在这种模式下会直接开始在当前线程下执行,直到运行到第一个挂起点。这听起来有点像ATOMIC
,不同之处在于UNDISPATCHED
是不经过任何调度器就开始执行的,也就是直接在当前线程执行。在遇到挂起点之后的执行,将取决于挂起点本身的逻辑和协程上下文中的调度器。
异常处理
协程上下文中的CoroutineExceptionHandler
。是协程上下文中的一个Element
,用来捕获协程中未处理的异常
kotlin
val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
Log.d("exceptionHandler", "${coroutineContext[CoroutineName]} :$throwable")
}
GlobalScope.launch(CoroutineName("异常处理") + exceptionHandler){
val job = launch{
Log.d("${Thread.currentThread().name}","我要开始抛异常了" )
throw NullPointerException("异常测试")
}
Log.d("${Thread.currentThread().name}", "end")
}
协程作用域
-
顶级作用域
--> 没有父协程的协程所在的作用域称之为顶级作用域。如:GlobalScope -
协同作用域
--> 在协程中启动一个协程,新协程为所在协程的子协程。子协程所在的作用域默认为协同作用域。此时子协程抛出未捕获的异常时,会将异常传递给父协程处理,如果父协程被取消,则所有子协程同时也会被取消。
javascript
GlobalScope.launch(CoroutineName("异常处理") + exceptionHandler + SupervisorJob()) {
val job = launch {
Log.d("${Thread.currentThread().name}", "我要开始抛异常了")
throw NullPointerException("异常测试")
}
Log.d("${Thread.currentThread().name}", "end")
}
主从作用域
官方称之为监督作用域
。与协同作用域一致,区别在于该作用域下的协程取消操作的单向传播性,子协程的异常不会导致其它子协程取消。但是如果父协程被取消,则所有子协程同时也会被取消。,主从作用域使用supervisorScope
javascript
GlobalScope.launch(CoroutineName("异常处理") + exceptionHandler + SupervisorJob()){
supervisorScope{
val job = launch{
Log.d("${Thread.currentThread().name}","我要开始抛异常了" )
throw NullPointerException("异常测试")
}
}
Log.d("${Thread.currentThread().name}", "end")
}