android 协程基础
协程构建器
launch和async构建器
launch
返回一个Job并且不会附带任何结果值
async
返回一个Deferred,Deferred是一个Job,可以使用await()在一个延期的值上得到它的最终结果
eg:通过await()来获取结果
GlobalScope.launch{
val job1 = launch {
delay(200)
println("job1")
}
println("job 测试顺序1")
val job2 = async {
delay(300)
println("job2")
"job2 result"
}
println("job 测试顺序2")
println(job2.await())
println("job 测试顺序3")
}
println("job 测试顺序0")
job 测试顺序0
job 测试顺序1
job 测试顺序2
job1
job2
job2 result
job 测试顺序3
join
join()方法可以在一个作业中中等待另一个作业结束后再进行其他操作,将上面代码改变下:
GlobalScope.launch{
val job1 = launch {
delay(1000)
println("job1")
}
println("job 测试顺序1")
val job2 = async {
delay(100)
println("job2")
"job2 result"
}
println("job 测试顺序2")
println(job2.await())
println("job 测试顺序3")
}
println("job 测试顺序0")
结果(未使用join方法):发现挂起时间短的先执行
2021-08-15 15:45:25.547 5604-5604/com.hujin.wan I/System.out: job 测试顺序0
2021-08-15 15:45:25.548 5604-5638/com.hujin.wan I/System.out: job 测试顺序1
2021-08-15 15:45:25.550 5604-5638/com.hujin.wan I/System.out: job 测试顺序2
2021-08-15 15:45:25.657 5604-5638/com.hujin.wan I/System.out: job2
2021-08-15 15:45:25.657 5604-5638/com.hujin.wan I/System.out: job2 result
2021-08-15 15:45:25.657 5604-5638/com.hujin.wan I/System.out: job 测试顺序3
2021-08-15 15:45:26.557 5604-5637/com.hujin.wan I/System.out: job1
如果需要job按照顺序执行的话,比如先执行job1,再执行job2或者job3:
eg:job1执行join方法
GlobalScope.launch{
val job1 = launch {
delay(1000)
println("job1")
}
job1.join()
println("job 测试顺序1")
val job2 = async {
delay(100)
println("job2")
"job2 result"
}
val job3 = launch {
delay(100)
println("job3")
}
println("job 测试顺序2")
println(job2.await())
println("job 测试顺序3")
}
println("job 测试顺序0")
结果:job1先执行,再执行job2和job3
2021-08-15 15:49:00.647 5730-5730/com.hujin.wan I/System.out: job 测试顺序0
2021-08-15 15:49:01.653 5730-5764/com.hujin.wan I/System.out: job1
2021-08-15 15:49:01.655 5730-5764/com.hujin.wan I/System.out: job 测试顺序1
2021-08-15 15:49:01.655 5730-5764/com.hujin.wan I/System.out: job 测试顺序2
2021-08-15 15:49:01.760 5730-5764/com.hujin.wan I/System.out: job3
2021-08-15 15:49:01.760 5730-5763/com.hujin.wan I/System.out: job2
2021-08-15 15:49:01.761 5730-5763/com.hujin.wan I/System.out: job2 result
2021-08-15 15:49:01.761 5730-5763/com.hujin.wan I/System.out: job 测试顺序3
eg:使用async/await来代替join,到达同样的效果
GlobalScope.launch{
val job1 = async {
delay(1000)
println("job1")
}
job1.await()
println("job 测试顺序1")
val job2 = async {
delay(100)
println("job2")
"job2 result"
}
val job3 = launch {
delay(100)
println("job3")
}
println("job 测试顺序2")
println(job2.await())
println("job 测试顺序3")
}
println("job 测试顺序0")
结果:到达同样的效果
2021-08-15 15:54:10.557 5913-5913/com.hujin.wan I/System.out: job 测试顺序0
2021-08-15 15:54:11.565 5913-5946/com.hujin.wan I/System.out: job1
2021-08-15 15:54:11.566 5913-5946/com.hujin.wan I/System.out: job 测试顺序1
2021-08-15 15:54:11.569 5913-5946/com.hujin.wan I/System.out: job 测试顺序2
2021-08-15 15:54:11.673 5913-5945/com.hujin.wan I/System.out: job3
2021-08-15 15:54:11.673 5913-5945/com.hujin.wan I/System.out: job2
2021-08-15 15:54:11.673 5913-5945/com.hujin.wan I/System.out: job2 result
2021-08-15 15:54:11.673 5913-5945/com.hujin.wan I/System.out: job 测试顺序3
组合并发
先看一场景,2个结果的相加:
private fun testCombination() {
GlobalScope.launch {
val time = measureTimeMillis {
val one = doOne()
val two = doTwo()
println("the result : ${one + two}")
}
println("the pay time $time")
}
}
suspend fun doOne():Int{
delay(1000)
return 15
}
suspend fun doTwo():Int{
delay(1000)
return 20
}
2021-08-15 16:52:35.496 6575-6604/com.hujin.wan I/System.out: the result : 35
2021-08-15 16:52:35.496 6575-6604/com.hujin.wan I/System.out: the pay time 2008
你会发现这两个结果内部是同步的,消耗时间是两个方法总时间,改造下:
private fun testCombination() {
GlobalScope.launch {
val time = measureTimeMillis {
val one = async { doOne() }
val two = async { doOne() }
println("the result : ${one.await() + two.await()}")
}
println("the pay time $time")
}
}
结果:
2021-08-15 16:57:07.113 6725-6758/com.hujin.wan I/System.out: the result : 30
2021-08-15 16:57:07.113 6725-6758/com.hujin.wan I/System.out: the pay time 1016
协程的启动模式
先看下launch的代码:
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
val newContext = newCoroutineContext(context)
val coroutine = if (start.isLazy)
LazyStandaloneCoroutine(newContext, block) else
StandaloneCoroutine(newContext, active = true)
coroutine.start(start, coroutine, block)
return coroutine
}
在我们协程启动的时候,可以设置一个启动模式,默认是CoroutineStart.DEFAULT
。
启动模式是指定协程在启动后是怎样的一个行为,一共有四种启动模式:
DEFAULT
协程创建后,立即开始调度,在调度前如果协程被取消,其将直接进入取消的响应状态
private fun testStartMode(){
runBlocking {
val job = launch(start = CoroutineStart.DEFAULT) {
println("job 被挂起")
delay(10000)
//下面这句不会被执行
println("job finished")
}
delay(1000)
job.cancel()
}
}
private fun testStartMode(){
val job = GlobalScope.launch(start = CoroutineStart.DEFAULT) {
//下面这句可能不会被执行
println("job finished")
}
job.cancel()
println("job cancel")
}
上面两种案例是不同的,一个是协程的执行期间被取消,第二个是调度前被取消的,可对照下面ATOMIC
。
ATOMIC
协程创建后,立即开始调度(不代表立即执行),协程执行到第一个挂起点之前不会响应取消(原子性)
private fun testStartMode(){
val job = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
//下面这句是会执行的
println("job finished")
}
job.cancel()
println("job cancel")
}
结果:
job cancel
job finished"
private fun testStartMode(){
val job = GlobalScope.launch(Dispatchers.Default,start = CoroutineStart.ATOMIC) {
println("job start")
delay(10)
//这句不会执行
println("job finished")
}
job.cancel()
println("job cancel")
}
结果:
job cancel
job start
LAZY
只有在需要的时候(调用协程start、join、await等)才会开始调度,如果调度前被取消,就不会执行,而是直接进入异常结束状态.一般先是用来定义好的。
eg:需要调用join才会执行协程体的内容,调用cancelAndJoin是无法执行的
private fun testStartMode(){
runBlocking {
val job = GlobalScope.launch(Dispatchers.Default,start = CoroutineStart.LAZY) {
//下面这句可能不会被执行
println("job start")
// delay(100)
// println("job finished")
}
job.join()
println("job cancel")
}
}
UNDISPATCHED
协程创建后立即在当前函数调用栈中执行,直到遇到第一个真正挂起的点.
协程的作用域构建器
runBlocking与coroutineScope
- runBlocking是常规函数,而coroutineScope是挂起函数
- 都会等待其协程体以及所有子协程结束,区别在于runBlocking方法会阻塞当前线程来等待,而coroutineScope只是挂起,会释放底层线程去干其他的
runBlocking 的例子,会阻塞等待延迟结束
private fun testStartCoroutine(){
runBlocking {
delay(1000)
Log.e("TAG","runBlocking[当前线程为:${Thread.currentThread().name}]")
}
Log.e("TAG","1.runBlocking[当前线程为:${Thread.currentThread().name}]")
}
2021-08-29 16:29:50.756 22304-22304/com.hujin.wan E/TAG: runBlocking[当前线程为:main]
2021-08-29 16:29:50.757 22304-22304/com.hujin.wan E/TAG: 1.runBlocking[当前线程为:main]
CoroutineScope 的例子,会挂起执行后面逻辑
private fun testStartCoroutine(){
CoroutineScope(Dispatchers.Main).launch {
delay(1000)
Log.e("TAG","CoroutineScope[当前线程为:${Thread.currentThread().name}]")
}
Log.e("TAG","1.CoroutineScope[当前线程为:${Thread.currentThread().name}]")
}
2021-08-29 16:40:16.385 22504-22504/com.hujin.wan E/TAG: 1.CoroutineScope[当前线程为:main]
2021-08-29 16:40:18.029 22504-22504/com.hujin.wan E/TAG: CoroutineScope[当前线程为:main]
coroutineScope与supervisorScope
- coroutineScope 一个协程失败了,所以其他兄弟协程也会被取消。内部的异常会向上传播,子协程未捕获的异常会向上传递给父协程,任何一个子协程异常退出,会导致整体的退出。
- supervisorScope 一个协程失败了,不会影响其他兄弟协程。内部的异常不会向上传播。
runBlocking {
supervisorScope {
val child1 = launch {
delay(400)
println("Child 1 is finished")
}
val child2 = launch {
println("Child 2 is sleeping")
delay(200)
println("Child 2 throws an exception")
throw AssertionError()
}
}
}
Job
Job对象
对于每一个创建的协程(通过launch或者async),会返回一个Job实例,是协程的唯一标示,并负责管理协程的生命周期。
一个任务可以包含一系列状态:新创建(New)、活跃(Active)、完成中(Completing)、已完成(Completed)、取消中(Cancelling)和已取消(Cancelled)。可以通过Job来访问这些熟悉。
private fun testJob() {
val job = GlobalScope.launch {
}
val active = job.isActive
val cancelled = job.isCancelled
val completed = job.isCompleted
}
Job的生命周期
如果协程处于活跃状态,在运行出错或者调用job.cancel()都会将当前任务置为取消中(Cancelling)状态(isActive = false,isCancelled = true)。当所以的子协程都完成后,协程会进入已取消(Cancelled)状态,此时isCompleted = true。
协程的取消
几个基本情况:
- 取消作用域会取消它的子协程
- 被取消的子协程并不会影响其余兄弟协程
- 协程通过抛出一个特殊的异常CancellationException来处理取消操作。(不会真的抛错)
- 所以coroutines中的挂起函数都是可以取消的。eg:withContext、delay等
子协程独立情景
runBlocking {
val request = launch {
//job1 使用 GlobalScope 来启动
GlobalScope.launch {
println("tag-test job1 execute thread:"+ Thread.currentThread().name)
delay(1000)
println("tag-test job1 is finished thread:"+ Thread.currentThread().name)
}
//job2 继承了父协程的上下文
launch {
delay(100)
println("tag-test job2 execute thread:"+ Thread.currentThread().name)
delay(1000)
println("tag-test job2 finished thread:"+ Thread.currentThread().name)
}
}
delay(500)
request.cancel()
delay(1000)
println("tag-test all is finished thread:"+ Thread.currentThread().name)
}
运行结果:job1是被独立出去了,即使父协程已经取消了。
job1 execute thread:DefaultDispatcher-worker-4
job2 execute thread:main
job1 is finished thread:DefaultDispatcher-worker-4
all is finished thread:main
作用域取消
runBlocking {
val request = launch {
repeat(3) { i ->
launch {
println("coroutine $i is start")
delay((i + 1) * 200L)
println("coroutine $i is done")
}
}
}
delay(300)
request.cancel()
println("coroutine request is done")
}
结果:作用域取消了子协程
coroutine 0 is start
coroutine 1 is start
coroutine 2 is start
coroutine 0 is done
coroutine request is done
cpu密集型取消
先看一种,cpu密集型取消不成功的情况,直接调用job.cancle()方法不会取消:
runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5){
if(System.currentTimeMillis() >= nextPrintTime){
println("job: i am sleeping ${i++}...")
nextPrintTime+=500
}
}
}
delay(1000)
println("main : i am tried of waiting!")
job.cancelAndJoin()
println("main: now i can quit")
}
测试结果:无法被取消
job: i am sleeping 0...
job: i am sleeping 1...
job:i am sleeping 2...
i am tried of waiting!
job: i am sleeping 3...
job: i am sleeping 4...
main: now i can quit
正确的退出方式,应该通过job的生命周期来退出cpu的计算逻辑,在调用cancle方法后,isActive会置为false,所以改动如下:
runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5 && isActive){
if(System.currentTimeMillis() >= nextPrintTime){
println("job: i am sleeping ${i++}...")
nextPrintTime+=500
}
}
}
delay(1000)
println("main : i am tried of waiting!")
job.cancelAndJoin()
println("main: now i can quit")
}
或者利用ensureActive()方法,效果一样的。
runBlocking {
val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
var nextPrintTime = startTime
var i = 0
while (i < 5 ){
ensureActive()
if(System.currentTimeMillis() >= nextPrintTime){
println("job: i am sleeping ${i++}...")
nextPrintTime+=500
}
}
}
delay(1000)
println("main : i am tried of waiting!")
job.cancelAndJoin()
println("main: now i can quit")
}
结果:成功取消,退出while循环
job: i am sleeping 0...
job: i am sleeping 1...
job:i am sleeping 2...
i am tried of waiting!
main: now i can quit
还可以调用yield()方法,这个会让出cpu的调度。
在finally中释放资源
在协程被取消的时候抛出CancellationException 异常,可以在结构体里添加try{...}finally{...}
中执行终结动作。
val job = launch {
try {
repeat(1000) { i ->
println("job : i am sleeping $i")
delay(400)
}
} finally {
println("job: i am running finally")
}
}
delay(2000L)
println("main: i am tired of waiting!")
job.cancelAndJoin()
println("main: now i can quit")
测试结果:
job : i am sleeping 0
job : i am sleeping 1
job : i am sleeping 2
job : i am sleeping 3
job : i am sleeping 4
main: i am tired of waiting!
job: i am running finally
main: now i can quit
不能被取消的任务
在结构体中的try-finally
中,再使用挂起操作,是不能被取消的。eg:
runBlocking {
val job = launch {
try {
repeat(1000) { i ->
println("job: i am sleeping $i...")
delay(500L)
}
} finally {
withContext(NonCancellable) {
println("job: i am running finally")
delay(5000)
println("job: and i have just delayed for 1 sec because i am non-cancellable")
}
}
}
delay(1400)
println("main: i am tired of waiting")
job.cancelAndJoin()
println("main: Now i can quit")
}
2021-09-13 22:32:04.842 11979-11979/com.hujin.wan I/System.out: job: i am sleeping 0...
2021-09-13 22:32:05.345 11979-11979/com.hujin.wan I/System.out: job: i am sleeping 1...
2021-09-13 22:32:05.846 11979-11979/com.hujin.wan I/System.out: job: i am sleeping 2...
2021-09-13 22:32:06.244 11979-11979/com.hujin.wan I/System.out: main: i am tired of waiting
2021-09-13 22:32:06.247 11979-11979/com.hujin.wan I/System.out: job: i am running finally
2021-09-13 22:32:11.249 11979-11979/com.hujin.wan I/System.out: job: and i have just delayed for 1 sec because i am non-cancellable
2021-09-13 22:32:11.249 11979-11979/com.hujin.wan I/System.out: main: Now i can quit
超时
可以使用withTimeout或者withTimeoutOrNull来对一个可能会超时的协程进行处理, withTimeoutOrNull 通过返回 null 来进行超时操作,从而替代抛出一个异常:
launch {
withTimeout(1300L){
repeat(10000){i->
println("i am sleeping $i ...")
delay(500)
}
}
}