【Kotlin】协程(之一)

内容提要:

  • 协程是什么
  • 协程怎么用
  • 协程有什么优点

-----------------------------------------------------------------------------------------------

正文:

一、协程是什么

  本质上,协程是轻量级的线程。

二、协程怎么用

1. 如何启动协程

【方法1】:用launch启动协程

import kotlinx.coroutines.*

fun main() {
    GlobalScope.launch { // 在后台启动一个新的协程并继续
        delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)
        println("World!") // 在延迟后打印输出
    }
    println("Hello,") // 协程已在等待时主线程还在继续
    Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}

输出:

Hello,
World!

【方法2】用runBlocking协程构建器启动协程

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    launch { // 在 runBlocking 作用域中启动一个新协程
        delay(1000L)
        println("World!")
    }
    println("Hello,")
}

 输出:

Hello,
World!

说明:

  • 协程出现了层级关系:runBlocking修饰的协程和launch修饰的协程,后者包含于前者之内,称之为前者的子协程。
  • 外部协程会等待子协程都执行完毕后才会结束。

【方法3】:用coroutineScope构建器启动协程——作用域构建器

import kotlinx.coroutines.*

fun main() = runBlocking { // this: CoroutineScope
    launch { 
        delay(200L)
        println("Task from runBlocking")
    }
    
    coroutineScope { // 创建一个协程作用域
        launch {
            delay(500L) 
            println("Task from nested launch")
        }
    
        delay(100L)
        println("Task from coroutine scope") // 这一行会在内嵌 launch 之前输出
    }
    
    println("Coroutine scope is over") // 这一行在内嵌 launch 执行完毕后才输出
}

输出:

Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over

 说明:

  • coroutineScope跟runBlocking一样,会等待子协程结束。
  • runBlocking会阻塞当前纯种来等待,而coroutineScope只是挂起,会释放底层纯种用于其他用途。
  • runBlocking是常规函数,而coroutineScope是挂起函数。
  • GlobalScope是一个全局协程,因此用GlobalScope.lanch启动的子协程的生命周期只受整个应用的生命周期限制。

2.什么是挂起函数

可以把launch{...}内部的代码块提取到独立的函数中

import kotlinx.coroutines.*

fun main() = runBlocking {
    launch { doWorld() }
    println("Hello,")
}

// 这是你的第一个挂起函数
suspend fun doWorld() {
    delay(1000L)  //调用其他挂起函数
    println("World!")
}

 说明:

  • 提取出来的函数要用suspend修饰,称为挂起函数
  • 挂起函数内也可以调用其他挂起函数,如delay()

3. 如何取消协程

一个用户也许关闭了一个启动了协程的界面,那么现在协程的执行结果已经不再被需要了,这时,它应该是可以被取消的。

val job = launch {
    repeat(1000) { i ->
        println("job: I'm sleeping $i ...")
        delay(500L)
    }
}
delay(1300L) // 延迟一段时间
println("main: I'm tired of waiting!")
job.cancel() // 取消该作业
job.join() // 等待作业执行结束
println("main: Now I can quit.")

输出: 

job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
main: Now I can quit.

说明:

  • launch函数返回了一个可以被用来取消运行中的协程的job
  • 通过job.cancel()取消作业
  • 通过job.join()等待执行结束
  • job.cancelAndJoin() = job.cancel() + job.join()

4.协程的超时

withTimeout(1300L) {
    repeat(1000) { i ->
        println("I'm sleeping $i ...")
        delay(500L)
    }
}

 说明:

  • 超时后为抛出一个TimeoutCancellationException异常
val result = withTimeoutOrNull(1300L) {
    repeat(1000) { i ->
        println("I'm sleeping $i ...")
        delay(500L)
    }
    "Done" // 在它运行得到结果之前取消它
}
println("Result is $result")

 说明:

  • 超时后返回null

5. 组合挂起函数

先定义两个函数:

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // 假设我们在这里做了一些有用的事
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // 假设我们在这里也做了一些有用的事
    return 29
}

a)【默认顺序】

val time = measureTimeMillis {
    val one = doSomethingUsefulOne()
    val two = doSomethingUsefulTwo()
    println("The answer is ${one + two}")
}
println("Completed in $time ms")

输出:

The answer is 42
Completed in 2006 ms

 b)【使用 async 并发】

val time = measureTimeMillis {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
    println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")

输出:

The answer is 42
Completed in 1029 ms

说明:

  • doSomethingUsefulOne和doSomethingUsefulTwo没有依赖,通过async实现并发执行。
  • async类似于launch,它启动了一个单独的协程。
  • 不同之处:launch返回一个job并且不附带任何结果,而async返回一个Deferred——一个轻量级的非阻塞future,这代表了一个将会在稍后提供结果的承诺。
  • 你可以使用 .await() 在一个延期的值上得到它的最终结果。
  • Deferred 也是一个job,因此也可以被取消。

c)【惰性启动的async】

val time = measureTimeMillis {
    val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
    val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
    // 执行一些计算
    one.start() // 启动第一个
    two.start() // 启动第二个
    println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")

输出:

The answer is 42
Completed in 1021 ms

说明:

  • async通过start = CoroutineStart.LAZY变为惰性的
  • 这个模式下,只有结果通过await获取的时候协程才会启动,或者在job的start函数调用的时候。
  • 如果我们只是在 println 中调用 await,而没有在单独的协程中调用 start,这将会导致顺序行为,直到 await 启动该协程 执行并等待至它结束。

d)【async风格的函数】

// somethingUsefulOneAsync 函数的返回值类型是 Deferred<Int>
fun somethingUsefulOneAsync() = GlobalScope.async {
    doSomethingUsefulOne()
}

// somethingUsefulTwoAsync 函数的返回值类型是 Deferred<Int>
fun somethingUsefulTwoAsync() = GlobalScope.async {
    doSomethingUsefulTwo()
}

// 注意,在这个示例中我们在 `main` 函数的右边没有加上 `runBlocking`
fun main() {
    val time = measureTimeMillis {
        // 我们可以在协程外面启动异步执行
        val one = somethingUsefulOneAsync()
        val two = somethingUsefulTwoAsync()
        // 但是等待结果必须调用其它的挂起或者阻塞
        // 当我们等待结果的时候,这里我们使用 `runBlocking { …… }` 来阻塞主线程
        runBlocking {
            println("The answer is ${one.await() + two.await()}")
        }
    }
    println("Completed in $time ms")
}

输出:

The answer is 42
Completed in 1121 ms

说明:

  • 这些 xxxAsync 函数不是 挂起 函数。它们可以在任何地方使用。
  • 然而,它们总是在调用它们的代码中意味着异步(这里的意思是 并发 )执行

e)【使用async的结构化并发】

suspend fun concurrentSum(): Int = coroutineScope {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
    one.await() + two.await()
}

 输出:

The answer is 42
Completed in 1041 ms

取消始终通过协程的层次结构来进行传递:

import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    try {
        failedConcurrentSum()
    } catch(e: ArithmeticException) {
        println("Computation failed with ArithmeticException")
    }
}

suspend fun failedConcurrentSum(): Int = coroutineScope {
    val one = async<Int> { 
        try {
            delay(Long.MAX_VALUE) // 模拟一个长时间的运算
            42
        } finally {
            println("First child was cancelled")
        }
    }
    val two = async<Int> { 
        println("Second child throws an exception")
        throw ArithmeticException()
    }
    one.await() + two.await()
}

l输出:

Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException

 说明:

  • 如果其中一个子协程(即 two)失败,第一个 async 以及等待中的父协程都会被取消:

 

三、协程有什么优点

【协程很轻量】

import kotlinx.coroutines.*

fun main() = runBlocking {
    repeat(100_000) { // 启动大量的协程
        launch {
            delay(1000L)
            print(".")
        }
    }
}

 它启动了10万个协程,并且在一秒钟之后,每个协程都输出一个点。如果是用线程来实现,将是一笔很大的开销。

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值