kotlin之协程(三)
1.CoroutineStart几种状态模式
默认是CoroutineStart.DEFAULT 立即执行
LAZY:稍后执行,只有执行job.start/join或者await才会开始执行
package day8Coroutines
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.system.measureTimeMillis
/**
* CoroutineStart.DEFAULT 立即执行
* LAZY稍后执行
*
*
*/
/**
* LAZY:稍后执行,只有执行job.start/join或者await才会开始执行
* job.start或者await两者执行的时间不一致,await会阻塞协程
*
*/
fun main(args: Array<String>) {
runBlocking {
val time = measureTimeMillis {
val v1 = async(start = CoroutineStart.LAZY) {
initValue()
}
val v2 = async(start = CoroutineStart.LAZY) {
initValue2()
}
v1.start()
v2.start()
//实现了并发,获取值
var result1 = v1.await()
var result2 = v2.await()
println("${result1 + result2}")
}
}
}
private suspend fun initValue(): Int {
delay(2000)
return 20
}
private suspend fun initValue2(): Int {
delay(2000)
return 50
}
2.async异步案例
package day8Coroutines
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
fun main() {
runBlocking {
var v = intSum()
print(v)
}
}
private suspend fun intSum(): Int = coroutineScope { // lamada表达式,最后一项作为返回值
val v1 = async { initValue() }
val v2 = async { initValue2() }
// return@coroutineScope v1.await() + v2.await()
v1.await() + v2.await()
}
private suspend fun initValue(): Int {
delay(2000)
return 20
}
private suspend fun initValue2(): Int {
delay(2000)
return 50
}
3.父子协程异常与取消问题
协程的取消总是会沿着协程层次体系向上运行传递。简单说,当前协程出现了问题,则会取消当前协程,继续父的协程
package day8Coroutines
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import java.lang.Exception
/**
* 父子协程的异常和取消问题
* 协程的取消总是会沿着协程层次体系向上运行传递
*
*/
fun main(args: Array<String>) {
runBlocking {
try {
failureComputation()
} finally {
println("结束")
}
}
}
private suspend fun failureComputation(): Int = coroutineScope {
val v1 = async<Int> {
delay(1000000)
50
}
val v2 = async<Int> {
Thread.sleep(2000)
throw Exception()
}
v1.await() + v2.await()
}
4.协程与线程之间的关系
简单说:默认会获取父协程的上下文CoroutineContext,如果是主线程,则主线程中的协程就在主线程中。 当然,Global和自行定义的另外说。
package day8Coroutines
import kotlinx.coroutines.*
import java.util.concurrent.Executors
/**
* 协程与线程之间的关系
* 协程上下文与分发器(Coroutine Context与Dispatcher)
* 协程总是会在某个上下文执行,实际是由CoroutineContext类型的一个实例来表示的,
* 协程本质是各种元素所构成的一个集合,主要包括job以及分发器 CoroutineDispatcher
* CoroutineDispatcher作用,可以将协程指定到一个具体的线程中执行
*
*
* 注意:如果不加任何限制Unified,具体在哪个线程执行时不确定的
*
*
* 注意:默认会获取父协程的上下文CoroutineContext,默认是EmptyCoroutineContext,它默认在共享线程池中
* 但如果有父协程,则如果不明确说明,默认获取父协程的上下文,在父协程的线程中执行
*
*/
fun main(args: Array<String>) {
runBlocking {
GlobalScope.launch { //后台线程池
println("5:${Thread.currentThread().name}")
}
launch {//main
println("1:${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {//不确定
println("2:${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { //共享线程池
println("3:${Thread.currentThread().name}")
}
launch(Executors.newSingleThreadExecutor().asCoroutineDispatcher()) {//main
println("4:${Thread.currentThread().name}")
}
}
}
5.Dispatchers分发器
package day8Coroutines
import kotlinx.coroutines.*
import java.util.concurrent.Executors
/**
* CoroutineDispatcher作用,可以将协程指定到一个具体的线程中执行
*
* Dispatchers.Unconfined 不定义具体哪个线程执行
* Dispatchers.Default 共享线程中执行
* Dispatchers.main 主线程中执行
* Dispatchers.IO 是为将阻塞的IO任务卸载到共享线程池而设计的
*
*
* * 注意:默认会获取父协程的上下文CoroutineContext,默认是EmptyCoroutineContext,它默认在共享线程池中
* 但如果有父协程,则如果不明确说明,默认获取父协程的上下文,在父协程的线程中执行
*
*/
fun main(args: Array<String>) {
runBlocking {
GlobalScope.launch {
println("5:${Thread.currentThread().name}")
}
launch {
println("1:${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) {
println("2:${Thread.currentThread().name}")
}
launch(Dispatchers.Default) {
println("3:${Thread.currentThread().name}")
}
launch(Executors.newSingleThreadExecutor().asCoroutineDispatcher()) {
println("4:${Thread.currentThread().name}")
}
}
}
6.Dispatchers.Unconfined
刚刚,我们说,Dispatchers.Unconfined的所在的线程是不确定的。 其实不对,我们是可以分析出来的。
Dispatchers.Unconfined 仅仅持续第一个线程,一旦持续到第一个挂起函数,则具体会到哪个线程执行是不确定的,是在挂起函数所在线程(默认default)
package day8Coroutines
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
/**
* Dispatchers.Unconfined 仅仅持续第一个线程,一旦持续到第一个挂起函数,则具体会到哪个线程执行是不确定的,是在挂起函数所在线程(默认default)
*
* 因此,它基本上不被使用
*/
fun main(args: Array<String>) {
runBlocking {
launch(Dispatchers.Unconfined) {
println("1:${Thread.currentThread().name}")
delay(100)
println("2:${Thread.currentThread().name}")
}
launch {
println("3:${Thread.currentThread().name}")
delay(2000)
println("4:${Thread.currentThread().name}")
}
}
}
1:main
3:main
2:kotlinx.coroutines.DefaultExecutor
4:main
7.协程在线程中切换
Android kotlin协程实际上是一个线程框架,底层是通过线程池来实现的,但是比线程更加灵活,由于比线程多了一个上下文,所以后台任务执行完毕后可以自动的切换回上下文协程中。这是线程比较难实现的。
简单说:就是通过上下文来实现线程的切换
package day8Coroutines
import kotlinx.coroutines.*
/**
* 协程在线程中切换
* CoroutineContext:四种协程上下文Unconfined、Default、newSingleThreadContext、runBlocking,此时使用Default调度器,使用一个JVM内的共享线程池
* 上下文:单线程池中执行,如果使用newSingleThreadContext.use线程池会自动关闭
*/
fun main(args: Array<String>) {
newSingleThreadContext("Context1").use { ctx1 ->
newSingleThreadContext("Context2").use { ctx2 ->
runBlocking {
logger("Start")
withContext(ctx2) {
logger("work in Context2")
}
logger("Back to Context")
}
}
}
println("你好")
runBlocking {
GlobalScope.launch(newSingleThreadContext("hello")) {
println("你好2")
}
}
}
private fun logger(logMessage: String) = println("${Thread.currentThread().name} $logMessage")
8.协程中线程切换案例
withContext:不会创建新的协程,在指定协程上运行挂起代码块,并挂起该协程直至代码块运行完成。简单说,就是在已有(并非原有)协程上挂起。
package day8Coroutines
import kotlinx.coroutines.*
fun main(args: Array<String>) {
runBlocking {
initData()
}
}
fun initData() {
GlobalScope.launch(Dispatchers.Main) {
ioCode1() //耗时操作1
// uiCode1() //更新UI操作1
ioCode2() //耗时操作2
// uiCode2() //更新UI操作2
ioCode3() //耗时操作3
// uiCode3() //更新UI操作3
}
}
suspend fun ioCode1() {
withContext(Dispatchers.IO) {
println("我是IO线程1==${Thread.currentThread().name}")
}
}
suspend fun ioCode2() {
withContext(Dispatchers.IO) {
println("我是IO线程2==${Thread.currentThread().name}")
}
}
suspend fun ioCode3() {
withContext(Dispatchers.IO) {
println("我是IO线程3==${Thread.currentThread().name}")
}
}
9.Job详解
package day8Coroutines
import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive
import kotlinx.coroutines.runBlocking
/**
* 如果获取上下文中job
* 协程的job是归属于上下文(Context)的一部分,kotlin为我们提供了一种手段来通过协程上下文获取到协程自身的JOB对象
* 我们可以通过coroutinesContext[Job]表达式来获取上下文中的job对象
*
* Job其实使用地是半生对象中,由于半生对象在kotlin中可以被隐藏持有
* 通过转换为java:
* Job job = (Job)$this$runBlocking.getCoroutineContext().get((Key)Job.Key);
Job job2 = (Job)$this$runBlocking.getCoroutineContext().get((Key)Job.Key);
*/
fun main(args: Array<String>) {
runBlocking {
val job: Job? = coroutineContext[Job]
val job2: Job? = coroutineContext[Job.Key]
println(job == job2)
println(job2?.isActive)
println(coroutineContext.isActive)
println(coroutineContext[Job]?.isActive)
println(job?.isActive)
}
}
10父子协程之间的关系
GlobalScope:特殊列外,其Job没有顶级父Job,看源码可以知道,它不会绑定到其他任何的Job中,所以是独立的协程,可以独立运行
其他父子协程:子协程的Job会成为父协程Job的一个孩子,当父协程取消时,子协程会通过递归方式一并取消子协程。
另外,如果不是手动取消的话,父协程,总是会等待子协程完毕之后才会结束。
package day8Coroutines
import kotlinx.coroutines.*
/**
* 父子协程关系:
* 1.当一个协程通过另外一个协程的CoroutineScope作用域来启动的,那么这个协程就会通过CoroutineScope.coroutineeContext来继承上下文
* 2.同时,新的协程的Job就会成为父协程Job的一个孩子,
* 3.所以,父协程取消时,子协程通过递归的方式一并取消
* 4.父协程,总是会等待子协程完毕之后才会结束,注意GlobalScope除外
*
* 特殊情况:GlobalScope启动的,其Job没有顶级父Job,看源码可以知道,它不会绑定到其他任何的Job中,所以是独立的协程,可以独立运行
* 所以官方建议:GlobalScope一般作为顶级Job来使用,而不是在某Job中使用
*/
fun main(args: Array<String>) {
runBlocking {
var request = launch {
GlobalScope.launch { //不受request控制
delay(2000)
println("顶级Job,不绑定任何Job中")
}
launch {
delay(500)
println("子Job")
delay(1000)
println("子job2")
}
launch(Dispatchers.Default) {
delay(2500)
println("子Default job2")
}
}
delay(1000)
request.cancelAndJoin()
delay(3000)
}
}
11.CoroutineName对协程进行命名
package day8Coroutines
import kotlinx.coroutines.*
/**
* CoroutineName对协程进行重新命名,更好输出日志
* CoroutineName 继承AbstractCoroutineContextElement,也是上下文
*/
fun main(args: Array<String>) {
runBlocking(CoroutineName("main")) {
val v = async(CoroutineName("coroutine")) {
delay(800)
println("你好")
30
}
launch(Dispatchers.Default) { }
launch(Dispatchers.Unconfined) { }
launch(newSingleThreadContext("context1")) {}
// launch(Dispatchers.Main) { } //在android中使用
launch(Dispatchers.IO) { }
println(v.await())
}
}
12.其他小知识
ensureActive()
在协程不在 active 状态时会立即抛出异常,所以也可以这样做: 针对密集型计算
while (i < 5) {
ensureActive()
…
}
println("你好")
30
}
launch(Dispatchers.Default) { }
launch(Dispatchers.Unconfined) { }
launch(newSingleThreadContext("context1")) {}
// launch(Dispatchers.Main) { } //在android中使用
launch(Dispatchers.IO) { }
println(v.await())
}
}
### 12.其他小知识
`ensureActive()` 在协程不在 active 状态时会立即抛出异常,所以也可以这样做: 针对密集型计算
```kotlin
while (i < 5) {
ensureActive()
…
}