文章目录

  • Kotlin 协程的启动
  • 添加依赖
  • 调试协程
  • 配置VM参数
  • 断点调试
  • 启动协程
  • GlobalScope.launch
  • launch源码
  • runBlocking
  • async-await
  • async优化并发
  • launch与async区别


Kotlin 协程的启动

添加依赖

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'

调试协程

配置VM参数

设置VM参数:-Dkotlinx.coroutines.debug,在输出 Thread.currentThread().name 时会带上协程的相关信息。

格式:
<线程名> @<协程名>#<自增点唯一ID>

例子:

fun main() = runBlocking {
    launch(Dispatchers.IO) {
        println("A: ${Thread.currentThread().name}")
    }
    println("B: ${Thread.currentThread().name}")
    Thread.sleep(1000L)
}

//B: main @coroutine#1
//A: DefaultDispatcher-worker-1 @coroutine#2

断点调试

查看Kotlin版本:

android kotlin计时器实现 kotlin suspendcoroutine_Kotlin

android kotlin计时器实现 kotlin suspendcoroutine_协程_02

打断点:勾选suspend、All,表示对协程生效

android kotlin计时器实现 kotlin suspendcoroutine_async_03

可以查看Coroutines面板信息:当前协程名是"coroutine:1",运行在““DefaultDispatcher-worker-1””线程上,当前状态是“RUNNING”。

android kotlin计时器实现 kotlin suspendcoroutine_android kotlin计时器实现_04

启动协程

GlobalScope.launch

fun main() {
    GlobalScope.launch {
        println("协程开始执行")	//1
        delay(1000L)
        println("hello world")	//2
    }
    println("abc")				//3
    Thread.sleep(2000L)
    println("执行完毕")			//4
}

//abc
//协程开始执行
//hello world
//执行完毕

说明:

一、

添加 Thread.sleep(2000L) 是为了防止通过 launch 创建的协程还没有开始执行,主线程就执行完了,程序就已经结束了。

这是因为 GlobalScope.launch 所依赖的线程是守护线程,守护线程当主线程结束的时候它也会跟着销毁。

类似于:

fun main() {
    //              守护线程
    //                 ↓
    thread(isDaemon = true) {
        Thread.sleep(1000L)
        println("Hello World!")
    }
}

二、

协程是 Fire-and-forget 的模型,因此打印的信息顺序是:3 1 2 4。

launch源码

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    ...
}

CoroutineScope.launch() :launch 是一个扩展函数,接受者类型是CoroutineScope,等价于它的成员方法。GlobalScope 是 Kotlin提供的一个 CoroutineScope 对象。

第一个参数 CoroutineContext :表示协程的上下文,默认值是EmptyCoroutineContext。可以用来指定协程的相关信息。

第二个参数 CoroutineStart :表示协程的启动模式。

  • DEFAULT:默认值,立即执行。
  • LAZY:懒加载执行。

第三个参数 suspend CoroutineScope.() -> Unit :协程执行的任务主体,函数类型的block,CoroutineScope的扩展函数。

runBlocking

fun main() {
    runBlocking {
        println("协程开始执行")	//1
        delay(1000L)
        println("hello world")	//2
    }
    println("abc")				//3
    Thread.sleep(2000L)
    println("执行完毕")			//4
}

//协程开始执行
//hello world
//abc
//执行完毕

说明:runBlocking 启动的协程会阻塞当前线程的执行,因此打印的顺序变成了:1 2 3 4。

案例二

fun main() {
    runBlocking {
        println("第一个协程:${Thread.currentThread().name}")
        delay(1000L)
        println("hello first")
    }
    runBlocking {
        println("第二个协程:${Thread.currentThread().name}")
        delay(1000L)
        println("hello second")
    }
    runBlocking {
        println("第三个协程:${Thread.currentThread().name}")
        delay(1000L)
        println("hello third")
    }
    println("end")
}

//第一个协程:main @coroutine#1
//hello first
//第二个协程:main @coroutine#2
//hello second
//第三个协程:main @coroutine#3
//hello third
//end

说明:runBlocking 启动的协程会阻塞当前线程的执行,因此打印会依次执行。请不要在生产环境当中使用 runBlocking。

返回值

fun main() {
    val result = runBlocking {
        delay(1000L)
        //可以省略 return@runBlocking
        return@runBlocking "hello world"
    }
    println("结果:${result}")
}

//结果:hello world

async-await

使用 async 启动协程后不会阻塞当前的执行流程,await()函数可以接收返回值,接收过程会执行“挂起”和“恢复”。

fun main() = runBlocking {
    println("runBlocking: ${Thread.currentThread().name}")  

    val deferred: Deferred<String> = async {
        println("async: ${Thread.currentThread().name}")
        delay(1000L)
        return@async "hello world"
    }
    
    println("async之后: ${Thread.currentThread().name}")
    
    val result = deferred.await()
    println("result: $result")
}

//runBlocking: main @coroutine#1
//async之后: main @coroutine#1
//async: main @coroutine#2
//result: hello world

async优化并发

//不使用async:
fun main() = runBlocking {
    suspend fun getName(): String {
        delay(1000L)
        return "小明"
    }

    suspend fun getAge(): Int {
        delay(2000L)
        return 18
    }

    suspend fun getAddress(): String {
        delay(3000L)
        return "北京市"
    }

    val results = mutableListOf<Any>()
    val time = measureTimeMillis {
        results.add(getName())
        results.add(getAge())
        results.add(getAddress())
    }
    println("耗时:$time")
    println(results)
}

//耗时:6009
//[小明, 18, 北京市]
//使用async:
fun main() = runBlocking {
    suspend fun getName(): String {
        delay(1000L)
        return "小明"
    }

    suspend fun getAge(): Int {
        delay(2000L)
        return 18
    }

    suspend fun getAddress(): String {
        delay(3000L)
        return "北京市"
    }

    var results = listOf<Any>()
    val time = measureTimeMillis {
        val result1 = async { getName() }
        val result2 = async { getAge() }
        val result3 = async { getAddress() }
        results = listOf<Any>(result1.await(), result2.await(), result3.await())
    }
    println("耗时:$time")
    println(results)
}

//耗时:3024
//[小明, 18, 北京市]

launch与async区别

源码区别:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit // 不同点1
): Job {} // 不同点2

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T // 不同点1
): Deferred<T> {} // 不同点2
  • launch 中的 block 的函数类型的返回值是 Unit,而 async 中的 block 的返回值是泛型T。
  • launch函数的返回值是Job类型,async函数的返回值是Deferred类型。
  • launch 就像射箭,async 就像钓鱼。

启动方式

是否阻塞

是否返回结果

应用场景

launch



Fire-and-forget

runBlocking



测试环境

async-await



获取协程执行的结果