Kotlin协程(2)✔️创建协程

  • kotlin 协程 API
  • 创建支持 kotlinx.coroutines 的项目
  • 第一个协程程序
  • launch 函数
  • Job 对象
  • runBlocking 函数
  • 挂起函数

kotlin 协程 API

Kotlin 支持协程,并提供了丰富的协程编程所需的 API,主要是三个方面的支持:
(1) 语言支持。kotlin 语言本身提供一些对协程的支持,例如 kotlin 中的 suspend 关键字可以声明一个挂起函数。
(2) 底层 API。kotlin 标准库中包含协程编程核心底层 API,来自于 kotlin. coroutines 包,这些底层 API 虽然也可以编写携程代码,但是使用起来非常麻烦,不推荐直接使用这些底层 API。
(3) 高级 API。高级 API 使用起来很简单,但 kotlin 标准库中没有高级 API,它来自于 kotlin 的扩展项目 kotlinx.coroutines 框架(https://github.com/Kotlin/kotlinx.coroutines),使用时需要额外配置项目依赖关系。

创建支持 kotlinx.coroutines 的项目

  kotlinx.coroutines 提供了协程开发的高级 API,使用起来比标准库中的底层 API 要简单得多。但使用 kotlinx.coroutines需要额外在项目中配置依赖关系。下面是在 build.gradle 文件中添加 kotlinx.coroutines 依赖关系的配置。

apply plugin: 'java-library'
apply plugin: 'kotlin'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines" // 1️⃣
}

sourceCompatibility = "8"
targetCompatibility = "8"

buildscript {
    ext.kotlin_version = '1.3.31' // 2️⃣
    ext.kotlinx_coroutines = '1.3.0-M1'
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
    }
}

repositories {
    mavenCentral()
}

compileKotlin {
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

compileTestKotlin {
    kotlinOptions {
        jvmTarget = "1.8"
    }
}

  上述代码第1️⃣行的 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinx_coroutines" 是刚刚添加的依赖关系。另外,还需要检测代码第2️⃣行的 ext.kotlin_version 是否为最新的 kotlin 版本。

第一个协程程序

  协程是轻量级的线程,因此协程也是由主线程管理的,如果主线程结束那么协程也就结束了。

fun main(args: Array<String>?) {
    GlobalScope.launch { // 1️⃣
        for (i in 1..10) {
            println("子协程执行第${i}次")
            val sleepTime = (random() * 1000).toLong()
            delay(sleepTime) // 2️⃣
        }
        println("子协程执行结束")
    }
    sleep(10 * 1000) // 3️⃣
    println("主程序结束...")
}

运行结果:

子协程执行第1次
子协程执行第2次
子协程执行第3次
子协程执行第4次
子协程执行第5次
子协程执行第6次
子协程执行第7次
子协程执行第8次
子协程执行第9次
子协程执行第10次
子协程执行结束
主程序结束...

  上述代码第1️⃣行的 GlobalScope.launch 函数创建并启动了一个协程,类似于线程的 thread 函数。代码第2️⃣行的 delay 函数是挂起协程,类似于线程的 sleep 函数,但不同的是 delay 函数不会阻塞线程,而 sleep 函数会阻塞线程。代码第3️⃣行时候让主线程休眠 10s。如果这里主线程不休眠,主线程就直接结束了,其他的线程或协程没有机会运行。

launch 函数

  上面示例中的 GlobalScope.launch 函数是非常重要的,它的定义如下:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job
  • CoroutineScope 可以理解为协程本身,包含了CoroutineContext

  • context 协程上下文,是一些元素的集合,主要包括 JobCoroutineDispatcher 元素,可以代表一个协程的场景。

  • CoroutineDispatcher协程调度器,决定协程所在的线程或线程池。它可以指定协程运行于特定的一个线程、一个线程池或者不指定任何线程(这样协程就会运行于当前线程)。coroutines-coreCoroutineDispatcher 有以下实现 Dispatchers.DefaultDispatchers.IODispatchers.MainDispatchers.UnconfinedUnconfined 就是不指定线程。launch 函数定义如果不指定 CoroutineDispatcher或者 没有其他的ContinuationInterceptor,默认的协程调度器就是Dispatchers.DefaultDefault 是一个协程调度器,其指定的线程为共有的线程池,线程数量至少为 2 最大与 CPU 数相同。

  • start 参数设置协程启动。

  • block 参数是协程体,类似于线程体,协程执行的核心代码在此编写,在协程体中执行的函数应该都是挂起函数,例如 delay 函数就是挂起函数。

  • launch 函数的返回是一个 Job 对象,Job 是协程要执行的任务,可以将 Job 对象看作协程本身,所有对协程的操作都是通过 Job 对象完成的,协程的状态和生命周期都是通过 Job 反应出来的。

Job 对象

Job 对象中常用的属性和函数如下:

  • isActive 属性:判断 Job 是否处于活动状态。
  • isCompleted 属性:判断 Job 是否处于完成状态。
  • isCancelled 属性:判断 Job 是否处于取消状态。
  • start 函数:开始 Job。
  • cancel 函数:取消 Job。
  • join 函数:使当前协程处于等待状态,直到 Job 完成,join 是一个挂起函数,只能在协程体或其他的挂起函数中调用。
fun main(args: Array<String>?) {
    val job = GlobalScope.launch {
        for (i in 1..10) {
            println("子协程执行第${i}次")
            val sleepTime = (random() * 1000).toLong()
            delay(sleepTime)
        }
        println("子协程执行结束")
    }
    println(job.isActive)
    println(job.isCompleted)
    sleep(10 * 1000)
    println("主程序结束...")
    println(job.isCompleted)
}
// 运行结果:
true
false
子协程执行第1次
子协程执行第2次
子协程执行第3次
子协程执行第4次
子协程执行第5次
子协程执行第6次
子协程执行第7次
子协程执行第8次
子协程执行第9次
子协程执行第10次
子协程执行结束
主程序结束...
true

runBlocking 函数

  前面的例子为了保持其他线程处于活动状态,示例中都使用了 sleep 函数。sleep 函数是线程提供的函数,最好不要在协程中使用,应该使用协程自己的 delay 函数,但 delay 是挂起函数,必须在协程体 或 其他的挂起函数中使用。

fun main(args: Array<String>?) = runBlocking {
    GlobalScope.launch {
        for (i in 1..10) {
            println("子协程执行第${i}次")
            val sleepTime = (random() * 1000).toLong()
            delay(sleepTime)
        }
        println("子协程执行结束")
    }
    delay(10 * 1000) // 1️⃣
    println("主程序结束...")
}
// 运行结果
子协程执行第1次
子协程执行第2次
子协程执行第3次
子协程执行第4次
子协程执行第5次
子协程执行第6次
子协程执行第7次
子协程执行第8次
子协程执行第9次
子协程执行第10次
子协程执行结束
主程序结束...

  上述代码将 main 代码放到 runBlocking 函数中,runBlocking 函数也是启动并创建一个协程,可以与顶层函数一起使用。代码第1️⃣行使用 delay 函数挂起主协程。

挂起函数

  如果开发人员需要编写一个挂起函数,可以使用 suspend 关键字声明,语法如下:

suspend fun 函数名(参数列表): 返回类型 {
    // 函数体
}

  注意:挂起函数只能在协程体中 或 其他的挂起函数中调用,不能在普通函数中调用。

  挂起函数不仅可以是顶层函数,还可以是成员函数和抽象函数,子类重写挂起函数后还应该是挂起的。

abstract class SuperClass {
    suspend abstract fun run()
}

class SubClass: SuperClass() {
    override suspend fun run() { }
}

  上述代码 SubClass 类实现了抽象类 SuperClass 的抽象挂起函数 run,重写后它还是挂起函数。

fun main(args: Array<String>?) = runBlocking {
    GlobalScope.launch {
        run("job1")
    }
    GlobalScope.launch {
        run("job2")
    }
    delay(10 * 1000)
    println("主程序结束...")
}

suspend fun run(name:String) {
    for (i in 1..10) {
        println("子协程 ${name} 执行第${i}次")
        val sleepTime = (random() * 1000).toLong()
        delay(sleepTime)
    }
    println("子协程 ${name} 执行结束")
}
// 运行结果
子协程 job1 执行第1次
子协程 job2 执行第1次
子协程 job1 执行第2次
子协程 job1 执行第3次
子协程 job1 执行第4次
子协程 job2 执行第2次
子协程 job2 执行第3次
子协程 job1 执行第5次
子协程 job1 执行第6次
子协程 job1 执行第7次
子协程 job2 执行第4次
子协程 job2 执行第5次
子协程 job2 执行第6次
子协程 job1 执行第8次
子协程 job1 执行第9次
子协程 job2 执行第7次
子协程 job2 执行第8次
子协程 job1 执行第10次
子协程 job1 执行结束
子协程 job2 执行第9次
子协程 job2 执行第10次
子协程 job2 执行结束
主程序结束...
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容