Kotlin-Coroutines-协程

基本概念

[官方github] https://github.com/Kotlin/kotlinx.coroutines
[官方API] https://kotlin.github.io/kotlinx.coroutines/
[RxCoroutine] https://github.com/Kotlin/kotlinx.coroutines/blob/master/reactive/kotlinx-coroutines-rx2/README.md
[官方示例] https://github.com/Kotlin/kotlinx.coroutines/tree/master/core/kotlinx-coroutines-core/src/test/kotlin/guide
[官方文档] https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md
https://github.com/Kotlin/kotlinx.coroutines/blob/master/reactive/coroutines-guide-reactive.md
[介绍基于JVM的协程机制] https://kotlinlang.org/docs/tutorials/coroutines-basic-jvm.html

协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
为了避免阻塞操作,Kotlin提供了轻量级的、可控的,协程挂起的操作;
协程是通过编译技术实现,不需要虚拟机或者操作系统的支持,通过在编译期插入相关代码来实现的;
协程的主要目的,是将原来“利用Thread和回调函数实现的异步操作”,简化成看似同步的书写方式来实现;
被suspend修饰的函数,称为挂起函数;
挂起函数只能被挂起函数 或者 协程调用;
基本上,每个挂起函数都转换为状态机;

在挂起协程前,下一状态和相关局部变量等被存储在编译器生成的类字段中;
在恢复该协程时,恢复局部变量并且状态机从挂起点接着后面的状态往后执行;
被挂起的协程,是作为Continuation对象来存储和传递,Continuation中持有协程挂起状态与局部变量;

线程是CPU的最小执行单元;
协程不是线程的取代者,而是抽象在线程之上的,协程需要线程来承载运行;
协程是组织好的代码流程,但协程不会直接使用线程,协程直接利用的是执行器(Interceptor);
执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程;
实质上协程是一种轻量级的可控制的用户线程,基于状态机实现,有协程构造器启动;

配置gradle

● 配置gradle
https://github.com/Kotlin/kotlinx.coroutines
https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-guide-ui.md#android

compile ‘org.jetbrains.kotlinx:kotlinx-coroutines-core:0.19.3’
compile “org.jetbrains.kotlinx:kotlinx-coroutines-android:0.19.3”

● gradle.properties
添加属性
kotlin.coroutines=enable

入门示例

延时2秒之后,吐司提示

import kotlinx.coroutines.experimental.android.UI

override fun onClick(v: View) {
    val id = v.id
    if (id == R.id.bt1) {
        coroutineFun()
        return
    }

}

fun coroutineFun() {
    launch(UI) {
        delay(2000L)
        ToastUtil.shortCenter("hello UI thread = ${BaseUtil.isUIThread()}")  // true
    }
}

async 与 Deferred

// New01Activity
if (R.id.bt3 == id) {
    "launch 之外-前".logI()
    launch(UI) {
        "主线程 ${BaseUtil.isUIThread()}".logI()
        val await = Sample01().testAsync().await()
        "主线程 ${BaseUtil.isUIThread()}".logI()
        ToastUtil.shortCenter(await)
    }
    "launch 之外-后".logI()
    return
}

// Sample01 
suspend fun testAsync() = async {
    "主线程 ${BaseUtil.isUIThread()}".logI()
    delay(2000)
    "主线程 ${BaseUtil.isUIThread()}".logI()
    "我是返回结果"
}

执行结果

10:06:27.611 25054-25054/[ (New01Activity.kt:37) #onClick] launch 之外-前
10:06:27.621 25054-25054/[ (New01Activity.kt:44) #onClick] launch 之外-后
10:06:27.623 25054-25054/[ (New01Activity.kt:39) New01Activity$onClick$2#doResume] 主线程 true
10:06:27.630 25054-25091/[ (Sample01.kt:39) Sample01$testAsync$2#doResume] 主线程 false
10:06:29.634 25054-25091/[ (Sample01.kt:41) Sample01$testAsync$2#doResume] 主线程 false
10:06:29.636 25054-25054/[ (New01Activity.kt:42) New01Activity$onClick$2#doResume] 主线程 true

API描述

主要函数:delay、async、launch、withContext、withTimeout、ticker、select、produce、broadcast、actor、runBlocking …

主要函数类:CoroutineStart、CoroutineDispatcher、Deferred、Job、Channel、Mutex …

async

在函数体内,异步 创建一个协程对象,以Deferred类对象的形式,返回一个预结果值;
当结果对象,即Deferred对象调用cancel后,协程会被取消;
如果协程的context引用的是 parent 的context,那么job也是parent的子job;
默认情况下,协程会被立刻执行,还可以配置start的参数值,选择 CoroutineStart.LAZY 等;
如果start=CoroutineStart.LAZY; 协程在Job.join 之后 或者 Deferred.await之后会被立即执行;

launch

启动一个协程,不会阻塞当前线程,同时 以Job类对象形式,返回一个协程的引用;
当Job调用cancel后,协程会被取消;
如果协程的context引用的是 parent 的context,那么job也是parent的子job;
默认情况下,协程会被立刻执行,还可以配置start的参数值,选择CoroutineStart.LAZY等;
如果start=CoroutineStart.LAZY; 协程在Job.join 之后会被立即执行;

● context 协程上下文;
● start 协程启动选项;
● block 协程真正要执行的代码块,必须是suspend修饰的挂起函数;
● return 函数返回一个Job类型,Job是协程创建的后台任务的概念,它持有该协程的引用;

launch(UI) 代码块是主线程,代码块可以执行耗时操作;
launch(CommonPool) 代码块是子线程,代码块可以执行耗时操作;

launch 的执行顺序

fun testLaunch2() {
    "A主线程 ${BaseUtil.isUIThread()}".logI() //A  
    launch(UI) {
        "B主线程 ${BaseUtil.isUIThread()}".logI()  // true
        delay(5000L)
        "B 2主线程 ${BaseUtil.isUIThread()}".logI()  // true
    }
    "C主线程 ${BaseUtil.isUIThread()}".logI()  // C  
    for (i in 0..399) {
        i.logI()
    }
}

先执行A处代码;
在执行C处代码;
最后执行B处代码;
不管C处代码,需要执行多久,都是如此,要等UI线程全部执行完成,才会执行launch{ }
launch(UI){} 代码块是主线程,仍然可以执行耗时操作,并且在代码块内,是顺序执行的;

fun testLaunch() {
    "A主线程 ${BaseUtil.isUIThread()}".logI()
    launch {
        "B主线程 ${BaseUtil.isUIThread()}".logI()  // false
        delay(300L)
        "B 2主线程 ${BaseUtil.isUIThread()}".logI()  // false
    }
    "C主线程 ${BaseUtil.isUIThread()}".logI()
    for (i in 0..99) {
        i.logI()
    }
}

执行结果

2018-07-23 00:11:53.578 9391-9391/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:14) #testLaunch] A主线程 true
2018-07-23 00:11:53.579 9391-9391/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:18) #testLaunch] C主线程 true
2018-07-23 00:11:53.580 9391-9391/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:20) #testLaunch] 0
2018-07-23 00:11:53.580 9391-9391/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:20) #testLaunch] ...
2018-07-23 00:11:53.587 9391-9391/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:20) #testLaunch] 21
2018-07-23 00:11:53.587 9391-9450/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:16) Sample04$testLaunch$1#doResume] B主线程 false
2018-07-23 00:11:53.580 9391-9391/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:20) #testLaunch] ...
2018-07-23 00:11:53.587 9391-9391/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:20) #testLaunch] 99
2018-07-23 00:18:11.957 9593-9630/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:19) Sample04$testLaunch$1#doResume] B 2主线程 false

结果分析:
A处代码最先执行;
C处代码开始执行;
launch{}代码块,涉及状态机的状态保存和切换,最后执行;
但是C处的代码没有直接结束,launch{}代码块就开始执行的情况,还是很常见的;
和上面的launch(UI) 的执行结果是不一样的;

利用parent参数,关联主线程生命周期

fun testLaunch5() {
    "A主线程 $isUiThread".logI()
    val job = Job()
    launch {
        repeat(5) { index ->
            launch(UI, parent = job, onCompletion = {
                "结束,$it".logW()
            }) {
                "$index B主线程 $isUiThread".logI()
                delay(3, TimeUnit.SECONDS)
                "$index B2主线程 $isUiThread".logI()
                delay(5, TimeUnit.SECONDS)
                "$index B3主线程 $isUiThread".logI()
            }
        }
        "C主线程 $isUiThread".logI()
        delay(4, TimeUnit.SECONDS)
        "D主线程 $isUiThread".logI()
        job.cancelAndJoin()
        "E主线程 $isUiThread".logI()
    }
}

运行结果:

07-24 23:38:42.594 13401-13401/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:88) #testLaunch5] A主线程 true
07-24 23:38:42.604 13401-13401/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:95) Sample04$testLaunch5$1$1$2#doResume] 0 B主线程 true
07-24 23:38:42.605 13401-13401/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:95) Sample04$testLaunch5$1$1$2#doResume] 1 B主线程 true
07-24 23:38:42.606 13401-13401/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:95) Sample04$testLaunch5$1$1$2#doResume] 2 B主线程 true
07-24 23:38:42.606 13401-13447/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:102) Sample04$testLaunch5$1#doResume] C主线程 false
07-24 23:38:42.606 13401-13401/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:95) Sample04$testLaunch5$1$1$2#doResume] 3 B主线程 true
07-24 23:38:42.606 13401-13401/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:95) Sample04$testLaunch5$1$1$2#doResume] 4 B主线程 true
07-24 23:38:45.610 13401-13401/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:97) Sample04$testLaunch5$1$1$2#doResume] 0 B2主线程 true
07-24 23:38:45.613 13401-13401/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:97) Sample04$testLaunch5$1$1$2#doResume] 1 B2主线程 true
07-24 23:38:45.615 13401-13401/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:97) Sample04$testLaunch5$1$1$2#doResume] 2 B2主线程 true
07-24 23:38:45.616 13401-13401/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:97) Sample04$testLaunch5$1$1$2#doResume] 3 B2主线程 true
07-24 23:38:45.618 13401-13401/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:97) Sample04$testLaunch5$1$1$2#doResume] 4 B2主线程 true
07-24 23:38:46.608 13401-13447/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:104) Sample04$testLaunch5$1#doResume] D主线程 false
07-24 23:38:46.614 13401-13401/com.alex.andfun.coroutine W/LogTrack: [ (Sample04.kt:93) Sample04$testLaunch5$1$1$1#invoke] 结束,kotlinx.coroutines.experimental.JobCancellationException: Child job was cancelled because of parent failure; job=StandaloneCoroutine{Cancelled}@2eff9080
07-24 23:38:46.618 13401-13401/com.alex.andfun.coroutine W/LogTrack: [ (Sample04.kt:93) Sample04$testLaunch5$1$1$1#invoke] 结束,kotlinx.coroutines.experimental.JobCancellationException: Child job was cancelled because of parent failure; job=StandaloneCoroutine{Cancelled}@23d7b6b9
07-24 23:38:46.620 13401-13401/com.alex.andfun.coroutine W/LogTrack: [ (Sample04.kt:93) Sample04$testLaunch5$1$1$1#invoke] 结束,kotlinx.coroutines.experimental.JobCancellationException: Child job was cancelled because of parent failure; job=StandaloneCoroutine{Cancelled}@89b5afe
07-24 23:38:46.623 13401-13401/com.alex.andfun.coroutine W/LogTrack: [ (Sample04.kt:93) Sample04$testLaunch5$1$1$1#invoke] 结束,kotlinx.coroutines.experimental.JobCancellationException: Child job was cancelled because of parent failure; job=StandaloneCoroutine{Cancelled}@1e53525f
07-24 23:38:46.625 13401-13401/com.alex.andfun.coroutine W/LogTrack: [ (Sample04.kt:93) Sample04$testLaunch5$1$1$1#invoke] 结束,kotlinx.coroutines.experimental.JobCancellationException: Child job was cancelled because of parent failure; job=StandaloneCoroutine{Cancelled}@210d34ac
07-24 23:38:46.627 13401-13447/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:106) Sample04$testLaunch5$1#doResume] E主线程 false

代码讲解:
在上述代码中, 首先定义一个 job,作为主的job;
在Activity的onDestroy函数中,调用job.cancel 就可以做到取消job以及其所有的子协程;

withContext

val userId = withContext(CommonPool) { getEmailMessage(4000L, userId) }
等同于
async(CommonPool) { getEmailMessage(4000L, userId) }.await()

withTimeout

超时时间

private fun coroutineFun() = runBlocking {
    val result = withTimeoutOrNull(1300) {
        "A".logI()
        delay(500)
        "B".logI()
        delay(500)
        "C".logI()
        delay(500)
        "D".logI()
        delay(500)
        "E".logI()
        try {

        } catch (e: Exception) {
            LogTrack.w(e.message)
        } finally {
            "finally 一定会运行的".logI()
        }
        "结束语"
    }
    LogTrack.w("result = $result")  // RESULT
}

● withTimeout
超时自动结束,但是不会看到抛出异常;
用try catch 可以捕获到异常信息;
不会执行到 RESULT 代码块;
● withTimeoutOrNull
超时自动结束,但是不会看到抛出异常;
用try catch 可以捕获到异常信息;
会执行到 RESULT 代码块,但是result为null;

ticker 定时器

fun testTicker() {
    launch(UI, parent = mainJob) {
        val tickerChannel = ticker(1, TimeUnit.SECONDS, initialDelay = 0)
        for (channel in tickerChannel) {
            currentTimeMillis.logI()
        }
    }
}

解释: 每间隔1秒,发一个事件,每个事件之间间隔0秒;

简单封装

```
/** * 计时器; * @param unit delay 和 initialDelay 的时间单位 * @param delay 相邻事件之间的delay时间 * @param initialDelay 在initialDelay时间之后,开始第一个事件 * @param context producing coroutine 的上下文 * @param uiBlock 回调在ui线程的代码块 * */
@Suppress("NAME_SHADOWING")
fun ticker(delay: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, initialDelay: Long = 0L,
           context: CoroutineContext = EmptyCoroutineContext,
           mode: TickerMode = TickerMode.FIXED_PERIOD, parent: Job? = null, uiBlock: () -> Unit) = launch(UI, parent = parent) {
    val receiveChannel = kotlinx.coroutines.experimental.channels.ticker(delay, unit, initialDelay, context, mode)
    for (unit in receiveChannel) {
        uiBlock.invoke()
    }
}

试用一下

fun testTicker2() {
    ticker(1, TimeUnit.SECONDS, parent = mainJob) {
        "主线程 $isUiThread$currentTimeMillis".logI()  // 主线程
    }
}

select 选择器

select表达式能够同时等待多条协程,并且选择第一个运行的协程。
select是一个协程方法,他会暂停当前协程,等到select返回

示例 1

发送事件

private fun firstProduce() = produce {
    for (i in 0..5) {
        send("first $i")
        delay(1500L)
    }
}

private fun secondProduce() = produce {
    for (i in 0..5) {
        send("second $i")
        delay(2000L)
    }
}

直接处理

private suspend fun handleChannel(firstChannel: ReceiveChannel, secondChannel: ReceiveChannel) {
    launch {
        for (result in firstChannel) {
            "A 收到 $result".logI()
        }
    }
    launch {
        for (result in secondChannel) {
            "B 收到 $result".logI()
        }
    }
}

运行结果

2018-07-28 21:30:08.664 31060-31231/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:53) Sample10$handleChannel$2#doResume] A 收到 first 0
2018-07-28 21:30:08.664 31060-31230/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:58) Sample10$handleChannel$3#doResume] B 收到 second 0
2018-07-28 21:30:10.165 31060-31231/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:53) Sample10$handleChannel$2#doResume] A 收到 first 1
2018-07-28 21:30:10.665 31060-31230/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:58) Sample10$handleChannel$3#doResume] B 收到 second 1
2018-07-28 21:30:11.669 31060-31231/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:53) Sample10$handleChannel$2#doResume] A 收到 first 2
2018-07-28 21:30:12.667 31060-31230/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:58) Sample10$handleChannel$3#doResume] B 收到 second 2
2018-07-28 21:30:13.170 31060-31231/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:53) Sample10$handleChannel$2#doResume] A 收到 first 3
2018-07-28 21:30:14.668 31060-31231/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:58) Sample10$handleChannel$3#doResume] B 收到 second 3
2018-07-28 21:30:14.672 31060-31230/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:53) Sample10$handleChannel$2#doResume] A 收到 first 4
2018-07-28 21:30:16.174 31060-31231/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:53) Sample10$handleChannel$2#doResume] A 收到 first 5
2018-07-28 21:30:16.670 31060-31230/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:58) Sample10$handleChannel$3#doResume] B 收到 second 4
2018-07-28 21:30:18.672 31060-31234/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:58) Sample10$handleChannel$3#doResume] B 收到 second 5

select

private suspend fun selectChannel(firstChannel: ReceiveChannel<String>, secondChannel: ReceiveChannel<String>) {
    select<String> {
        firstChannel.onReceive { result -> "A 收到 $result".logI()
            ""
        }
        secondChannel.onReceive { result -> "B 收到 $result".logI()
            ""
        }
    }
}

运行结果

2018-07-28 21:35:45.238 31318-31390/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:40) Sample10$selectChannel$2$1#doResume] A 收到 first 0
2018-07-28 21:35:45.240 31318-31390/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:44) Sample10$selectChannel$2$2#doResume] B 收到 second 0
2018-07-28 21:35:46.740 31318-31392/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:40) Sample10$selectChannel$2$1#doResume] A 收到 first 1
2018-07-28 21:35:47.242 31318-31390/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:44) Sample10$selectChannel$2$2#doResume] B 收到 second 1
2018-07-28 21:35:48.244 31318-31390/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:40) Sample10$selectChannel$2$1#doResume] A 收到 first 2
2018-07-28 21:35:49.245 31318-31390/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:44) Sample10$selectChannel$2$2#doResume] B 收到 second 2
2018-07-28 21:35:49.746 31318-31392/com.alex.andfun.coroutine I/LogTrack: [ (Sample10.kt:40) Sample10$selectChannel$2$1#doResume] A 收到 first 3

分析
select执行结果有7条,
直接处理A有6条,B 有6条;

produce

启动一个新的协程,产生数据流,通过管道流发送数据;
函数的返回值是ProducerJob类对象,即为协程对象;

if (R.id.bt9 == id) { LogTrack.i("before launch") val job = launch(UI) { Sample01().createProduce().consumeEach { "主线程 ${BaseUtil.isUIThread()} , 收到 $it".logI() } LogTrack.i("consumeEach finish") } LogTrack.i("after launch") return } fun createProduce() = produce { "主线程 ${BaseUtil.isUIThread()}".logI() send("A") delay(1000L) send("B") delay(2000L) send("C") delay(3000L) send("D") }

执行结果

2018-07-23 13:01:17.859 6990-6990/? I/LogTrack: [ (New01Activity.kt:66) #onClick] before launch
2018-07-23 13:01:17.868 6990-6990/? I/LogTrack: [ (New01Activity.kt:73) #onClick] after launch
2018-07-23 13:01:17.875 6990-7028/? I/LogTrack: [ (Sample01.kt:50) Sample01$createProduce$1#doResume] 主线程 false
2018-07-23 13:01:17.876 6990-6990/? I/LogTrack: [ (New01Activity.kt:70) New01Activity$onClick$job$1#doResume] 主线程 true  , 收到 A
2018-07-23 13:01:18.880 6990-6990/? I/LogTrack: [ (New01Activity.kt:70) New01Activity$onClick$job$1#doResume] 主线程 true  , 收到 B
2018-07-23 13:01:20.880 6990-6990/? I/LogTrack: [ (New01Activity.kt:70) New01Activity$onClick$job$1#doResume] 主线程 true  , 收到 C
2018-07-23 13:01:23.882 6990-6990/? I/LogTrack: [ (New01Activity.kt:70) New01Activity$onClick$job$1#doResume] 主线程 true  , 收到 D
2018-07-23 13:01:23.883 6990-6990/? I/LogTrack: [ (New01Activity.kt:71) New01Activity$onClick$job$1#doResume] consumeEach finish

broadcast 广播

fun testBroadcast() {
    launch {
        val broadcastChannel = broadcast {
            delay(2000)
            send("A")
            delay(1000)
            send("B")
            delay(1500)
            send("C")
        }
        val receiveChannel = broadcastChannel.openSubscription()
        for (element in receiveChannel) {
            "$element $currentTimeMillis".logI()
        }
    }

}

actor

fun testActor() {

    val sendChannel = createSendChannel()
    launch {
        delay(1000L)
        sendChannel.send("100")
        delay(1500L)
        sendChannel.send("102")
        delay(2000L)
        sendChannel.send("106")
    }

}

private fun createSendChannel(): SendChannel = actor(UI) {
    for (message in channel) {
        "B主线程 $isUiThread, $message".logI()
    }
}

执行结果

2018-07-29 22:11:59.933 9270-9270/com.alex.andfun.coroutine I/LogTrack: [ (Sample12.kt:32) Sample12$createSendChannel$1#doResume] B主线程 true, 100
2018-07-29 22:12:01.434 9270-9270/com.alex.andfun.coroutine I/LogTrack: [ (Sample12.kt:32) Sample12$createSendChannel$1#doResume] B主线程 true, 102
2018-07-29 22:12:03.437 9270-9270/com.alex.andfun.coroutine I/LogTrack: [ (Sample12.kt:32) Sample12$createSendChannel$1#doResume] B主线程 true, 106

runBlocking

runBlocking函数不是用来当作普通协程函数使用的,它的设计主要是用来桥接普通阻塞代码和挂起风格-非阻塞代码的;
主要用来跑 psvm 的main函数,或者test函数的;
如果直接在main函数跑协程代码,可能看不到执行结果,因为这个时候,主线程已经结束了,故此 需要runBlocking代码块;
在runBlocking代码块,会在当前thread中执行,需要执行的携程函数,并等待携程执行完成;
如果runBlocking代码块跑在UI线程,出现耗时操作,一定会出现ANR异常的;

CoroutineStart

override fun onClick(v: View) {
    val id = v.id
    if (id == R.id.bt1) {
        "A".logI()
        launch(UI, CoroutineStart.UNDISPATCHED) {
            "B".logI()
            delay(3000)
            "C".logI()
        }
        "D".logI()
        return
    }
}

● CoroutineStart.UNDISPATCHED 执行结果:
A
B
D
C

● CoroutineStart.DEFAULT 执行结果:
A
D
B
C

CoroutineDispatcher

协程启动选项,是一个抽象类,其子类:
● Unconfined 和当前线程,保持一致;
● CommonPool 线程池,工作线程;
● HandlerContext 利用Handler实现的,Android的UI线程;

CommonPool

CommonPool是代表共享线程池,它的主要作用是用来调度计算密集型任务的协程的执行。它的实现使用的是java.util.concurrent包下面的API;
它首先尝试创建一个ForkJoinPool,ForkJoinPool是一个可以执行ForkJoinTask的ExcuteService, 它采用了work-stealing模式,
所有在池中的线程尝试去执行其他线程创建的子任务,这样很少有线程处于空闲状态,更加高效,如果不可用,就使用Executors来创建一个普通的线程池;

Job

join函数

挂起协程,一直等到job结束,当job正常执行一直到完成或者被cancel,挂起的协程会正常返回,不会抛出异常信息;
当协程已经被取消,或者已经完成,再调用join函数,就会抛出异常;
当外部因素使用当前job,调用cancel函数,会使得job.join() 之后的代码快被执行;

fun testLaunch4() {
    "A主线程 $isUiThread".logI()
    launch(UI) {
        val job = launch {
            delay(5000L)
            "B主线程 $isUiThread".logI()
            delay(5000L)
            "B1主线程 $isUiThread".logI()
            delay(8000L)
            "B2主线程 $isUiThread".logI()
            delay(5000L)
        }
        "C主线程 $isUiThread".logI()
        job.join()
        "D主线程 $isUiThread".logI()
        delay(6000L)
        "E主线程 $isUiThread".logI()
        job.cancel()
        "F主线程 $isUiThread".logI()
    }
}

执行结果

2018-07-24 19:49:03.585 10445-10445/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:59) #testLaunch4] A主线程 true
2018-07-24 19:49:03.598 10445-10445/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:70) Sample04$testLaunch4$1#doResume] C主线程 true
2018-07-24 19:49:08.603 10445-10482/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:63) Sample04$testLaunch4$1$job$1#doResume] B主线程 false
2018-07-24 19:49:13.606 10445-10482/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:65) Sample04$testLaunch4$1$job$1#doResume] B1主线程 false
2018-07-24 19:49:21.608 10445-10482/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:67) Sample04$testLaunch4$1$job$1#doResume] B2主线程 false
2018-07-24 19:49:26.612 10445-10445/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:72) Sample04$testLaunch4$1#doResume] D主线程 true
2018-07-24 19:49:32.620 10445-10445/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:74) Sample04$testLaunch4$1#doResume] E主线程 true
2018-07-24 19:49:32.621 10445-10445/com.alex.andfun.coroutine I/LogTrack: [ (Sample04.kt:76) Sample04$testLaunch4$1#doResume] F主线程 true

cancel

● 如果协程内,有释放时间片的操作,那么,在cancel之后,协程会被立刻取消掉;
● 如果协程内,一直是抢占式的操作,那么,在cancel之后,协程会在抢占式操作结束之后,被取消掉;
● 如果要在协程内,捕获cancel信息,可以用try catch来包裹内部代码块;

private fun delayFun() = runBlocking {
    val job = launch {
        try {
            repeat(6) {
                LogTrack.i("打印计数器 $it")
                delay(500)
            }
            "B".logI()
        } catch (e: Exception) {
            LogTrack.w(e.message)
        } finally {
            LogTrack.i("finally")
        }
    }
    delay(1300)
    "C".logI()
    job.cancel()
    job.join()
    "D".logI()
}

● 无论是 cancel 还是 cancelAndJoin,都会等待协程内部的finally代码块 执行完;

● 如果finally包裹NonCancellable,则会等待这段代码块执行完,再取消掉;

private fun delayFun() = runBlocking {
    val job = launch {
        try {
            repeat(6) {
                LogTrack.i("打印计数器 $it")
                delay(500)
            }
            "B".logI()
        } catch (e: Exception) {
            LogTrack.w(e.message)
        } finally {
            LogTrack.i("finally NonCancellable")
            run(NonCancellable) {
                LogTrack.i("finally A")
                delay(3000)
                LogTrack.i("finally B")
            }
        }
    }
    delay(1300)
    "C".logI()
//        job.cancelAndJoin()
    job.cancel()
    job.join()
    "D".logI()
}

计算密集型 cancel 无法取消任务
协程正工作在循环计算中,并且不检查协程当前的状态, 那么调用cancel来取消协程将无法停止协程的运行,可以这样解决

if (!isActive) {
    return@launch
}

Channel

示例代码

fun testChannel() {
    val channel = Channel()
    launch(UI) {
        launch {
            channel.send("A")
            delay(2, TimeUnit.SECONDS)
            channel.send("B")
            delay(1, TimeUnit.SECONDS)
            channel.send("C")
            channel.close()
        }
        for (result in channel) {
            "收到结果 $result".logI()
        }
    }
}

receive()

只接收一个事件

for (result in channel)

按顺序接收,处理所有事件,直到channel关闭

for (result in channel) {
    "收到结果 $result".logI()
}

close

关闭资源

fan-out

有一个SendChannel 多个 ReceiverChannel;
一个 SendChannel 每次只发一个事件;
如果一个事件,被其中一个ReceiverChannel消费掉,就不会再被其他ReceiverChannel处理,也就是其他ReceiverChannel的consumeEach不会能够迭代出事件;
并且处理事件,推荐使用 for (result in channel) 而不是 channel.consumeEach ;
理由: https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md
Also, pay attention to how we explicitly iterate over channel with for loop to perform fan-out in launchProcessor code.
Unlike consumeEach, this for loop pattern is perfectly safe to use from multiple coroutines.
If one of the processor coroutines fails, then others would still be processing the channel,
while a processor that is written via consumeEach always consumes (cancels) the underlying channel on its normal or abnormal termination.

for (result in channel) {
    "name = $name, 收到 $result,主线程 $isUiThread".logW()
}
channel.consumeEach { result ->
    "name = $name, 收到 $result,主线程 $isUiThread".logW()
}

fan-in

/** * 一个发送方,多个接收方 * */
fun oneReceiverMultipleSender() {
    val channel = Channel()
    launch(UI) {
        launch { sendString(channel, "foo", 200L) }  // 如果没有launch,收不到事件
        launch { sendString(channel, "BAR!", 500L) }  // 如果没有launch,收不到事件
        for (result in channel) {  // 一直发,一直收
            "收到 $result".logW()
        }
        repeat(6) {
            // 只收到前6个
            "收到 ${channel.receive()}".logW()
        }
    }
}

private suspend fun sendString(channel: SendChannel, s: String, time: Long) {
    while (true) {
        delay(time)
        channel.send(s)
    }
}

缓冲区

val channel = Channel(4) // 创建一个缓冲区容量为4的通道
channel.send(it) // 当缓冲区已满的时候, send将会挂起

Mutex

当多个协程操作一个变量时,也会存在安全问题,使用Mutex进行挂起

val mutex = Mutex()
var counter = 0
fun main(args: Array) = runBlocking {
    massiveRun(CommonPool) {
        mutex.lock()
        try { counter++ }
        finally { mutex.unlock() }
    }
    ...
}

使用线程安全数据结构

private val counter = AtomicInteger()
fun main(args: Array) = runBlocking {
    massiveRun(CommonPool) {
        counter.incrementAndGet()
    }
    ...
}

你可能感兴趣的:(kotlin)