本文简洁明了的介绍了协程的基础概念、挂起函数、作用域,附有演示代码以加深理解。
协程关键字
- CoroutineScope 协程的作用域
- CoroutineDispatcher 协程的调度器
- CoroutineContext 协程上下文
挂起函数
概念:使用关键词suspend修饰
delay
将当前协程挂起指定时间,但不会阻塞线程,必须在协程的作用域或者其他挂起函数中执行
withContext
必须在协程的作用域中调用,必须指定协程的上下文,函数的最后一行是返回值
GlobalScope.launch {
withContext(Dispatchers.Default) {
delay(2000)
"return string"
}
}
//运行结果:
return string
yield
挂起当前的协程,将当前协程分发到CoroutineDispatcher队列(当前协程的父协程),等待其他协程执行完/挂起,再执行先前的协程。
fun main(args: Array<String>) = runBlocking {
launch {
println("1")
yield()
println("2")
}
launch {
println("3")
yield()
println("4")
}
delay(5000)
}
//运行结果:
1
3
2
4
协程上下文
NonCancellable
始终处于活动状态的不可取消的任务,配合withContext一起使用。
使用场景:已经被cancel的协程,可以使用withContext(NonCancellable)继续执行协程中的任务, 用于重置对象及清理工作。
fun main(args: Array<String>) = runBlocking {
val job1 = GlobalScope.launch {
try {
delay(1000)
println("job 1")
} catch (e: Exception) {
e.printStackTrace()
} finally {
withContext(NonCancellable) {
delay(1000)
println("job: I'm running finally")
}
}
}
job1.cancel()
delay(5000)
}
//运行结果:
job: I'm running finally
可以看到
- 正在运行的协程被cancel后,会报异常JobCancellationException,可看到println("job 1")没有执行到
- finally中的代码有正常执行,有打印日志(job: I'm running finally),可使用withContext(NonCancellable)创建协程做一些清理工作
协程作用域
父子协程
A协程是使用B协程的CoroutineContext创建的,那么B就是A的父协程。协程默认是串行执行的,父协程会等待子协程执行完毕。
val job = GlobalScope.launch {
val job1 = launch {
println("job 1 start")
delay(1000)
println("job 1 end")
}
val job2 = GlobalScope.launch {
println("job 2 start")
delay(1000)
println("job 2 end")
}
}
job.cancel()
//运行结果:
job 1 start
job 2 start
job 2 end
结论:父协程取消后,子协程的任务也会被取消
以下三种方式创建的都是子协程
- withContext(Dispatchers.Default) { }
- launch { }
- launch(coroutineContext) { }
以下两种方式创建的则不是子协程
- CoroutineScope(Dispatchers.Default)
- GlobalScope.launch { }
CoroutineContext +
CoroutineContext使用+,使一个协程具有多个CoroutineContext特性
val job = GlobalScope.launch {
val job1 = GlobalScope.launch(Dispatchers.Default + coroutineContext) {
println("job 1 start")
delay(500)
println("job 1 end")
}
}
job.cancel()
//运行结果:
job 1 start
说明:使用 + 后,job1变成job的子协程,job被cancel后,job1也被停止了
去掉 + coroutineContext试一下
val job = GlobalScope.launch {
val job1 = GlobalScope.launch(Dispatchers.Default) {
println("job 1 start")
delay(500)
println("job 1 end")
}
}
job.cancel()
//运行结果:
job 1 start
job 1 end
结论:两个协程没有父子关系,互不影响,job取消后job1还可以继续执行
CoroutineContext+Job
使用CoroutineContext+Job后,可以使用Job统一管理协程
val job = Job()
val job1 = GlobalScope.launch(Dispatchers.Default + job) {
println("job 1 start")
delay(500)
println("job 1 end")
}
job.cancel()
//运行结果:
job 1 start
结论:job取消后,job1也被取消了。在 Android 开发中,Activity/Fragment 可以通过创建一个 Job 对象,并让该 Job 管理其他的协程。等到退出 Activity/Fragment 时,调用 job.cancel() 来取消协程的任务。
GlobalScope
主要分享GlobalScope的易错点,以下是错误的写法:
fun main(args: Array<String>) {
try {
GlobalScope.launch {
println(doSomething())
}
} catch (e:Exception) {
}
//一定要加这行代码,不然main都执行结束了,代码还没执行到doSomething()方法
Thread.sleep(100)
}
suspend fun doSomething(): String = withContext(Dispatchers.Default) {
throw RuntimeException("this is an exception")
"doSomething..."
}
此代码会抛出异常,try catch并没有捕获到异常,应该写在GlobalScope.launch {}里面
正确的写法:
fun main(args: Array<String>) {
GlobalScope.launch {
try {
println(doSomething())
} catch (e:Exception) {
}
}
Thread.sleep(100)
}
suspend fun doSomething(): String = withContext(Dispatchers.Default) {
throw RuntimeException("this is an exception")
"doSomething..."
}
参考文档:
[Kotlin协程取消] (juejin.cn/post/704371…)