Bootstrap

kotlinx.coroutines 使用

kotlin协程为线程切换框架

fun main(args: Array<String>) {
  GlobalScope.launch {
      delay(1000L)
      print("Coroutines")
  }
    print("Hello, ")
    Thread.sleep(2000L)
}
//输出Hello, Coroutines


这里我们用到了两个协程相关的方法

  •  GlobalScope.launch{}

      启动一个协程

  •  delay()

    挂起一个协程 ,类似于Thread.sleep 但是该方法只能在coroutine 中使用,不会对线程造成阻塞

fun main(args: Array<String>) {
  GlobalScope.launch {
      delay(1000L)
      print("Coroutines")
  }
    print("Hello, ")
    runBlocking {
        delay(2000L)
    }
}
//输出Hello, Coroutines
  • runBlocking{} 

    表达式阻塞线程

fun main(args: Array<String>)= runBlocking<Unit> {

    GlobalScope.launch {
        delay(1000L)
        print("Coroutines")
    }
    print("Hello, ")
    delay(2000L)

}

这个示例更合乎我们常用写法,显式声明了返回值为Unit,因为main()方法只能返回Unit

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

协程很轻量级,以上代码启动了10万个协程,换成线程是不能做到的

fun main(args: Array<String>)= runBlocking<Unit> {
    GlobalScope.launch {
       repeat(1000){
           println("GlobalScope$it")
           delay(1000L)
       }
    }
    delay(2100L) // 在延迟后退出
}

以下代码在GlobalScope中启动了一个长期运行的协程,该协程每秒输出“GlobalScope”一次,之后在主函数中延迟一段时间后返回。

这段代码输出了3次 GlobalScope

  • Job .cancel()

fun main(args: Array<String>)= runBlocking<Unit> {
  val job =  launch {
        repeat(1000){
            println("job: I'm sleeping $it ...")
            delay(1000L)
        }
    }
    delay(2100L) // 在延迟后退出
    println("main: I'm tired of waiting!")
    job.cancel()
    job.join()
    println("main: Now I can quit.")

}

取消这个协程,也可以使用cancelAndJoin 这个方法是cacel 和join的调用

val startTime = System.currentTimeMillis()
val job = launch(Dispatchers.Default) {
    var nextPrintTime = startTime
    var i = 0
    while (i < 5) { // 一个执行计算的循环,只是为了占用 CPU
        // 每秒打印消息两次
        if (System.currentTimeMillis() >= nextPrintTime) {
            println("job: I'm sleeping ${i++} ...")
            nextPrintTime += 500L
        }
    }
}
delay(1300L) // 等待一段时间
println("main: I'm tired of waiting!")
job.cancelAndJoin() // 取消一个任务并且等待它结束
println("main: Now I can quit.")

协程的取消是 协作 的。一段协程代码必须协作才能被取消。 所有 kotlinx.coroutines 中的挂起函数都是 可被取消的 。它们检查协程的取消, 并在取消时抛出 CancellationException。 然而,如果协程正在执行计算任务,并且没有检查取消的话,那么它是不能被取消的。

使占CPU的代码取消

我们有两种方法来使执行计算的代码可以被取消。第一种方法是定期调用挂起函数来检查取消。对于这种目的 yield是一个好的选择。 另一种方法是显式的检查取消状态。让我们试试第二种方法。

fun main(args: Array<String>)= runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (isActive) { // 可以被取消的计算循环
            // 每秒打印消息两次
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // 等待一段时间
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // 取消该任务并等待它结束
    println("main: Now I can quit.")

}

isActive 变量是一个可以使用在CoroutineScope中的扩展属性

当 suspending  functions 函数被取消的时候 会抛出CancellationException  我们可以用try{} finally{}  或Kotlin的use 函数

fun main(args: Array<String>)= runBlocking<Unit> {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            println("job: I'm running finally")
        }
    }
    delay(1300L) // 延迟一段时间
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // 取消该任务并且等待它结束
    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!
//job: I'm running finally
//main: Now I can quit.

join 和cancelAndJoin 都会等待finally的代码执行完

  • withContext 和 NoCancellable

如果你试图在finally 中使用 suspending functions 那么会抛出CancellationException 因为当前协程已经被取消了,一般来说这不是问题,所有良好的关闭操作(关闭文件、取消任务,或者关闭各种形式的通信频道)通常都是非阻塞式的,并不需要调用 suspending functions。当你需要在finally 中执行suspending functions 就需要使用 withContext 和 NoCancellable 上下文来处理

fun main(args: Array<String>)= runBlocking<Unit> {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            withContext(NonCancellable){
                println("job: I'm running finally")
                delay(1000L)
                println("job: And I've just delayed for 1 sec because I'm non-cancellable")
            }
        }
    }
    delay(1300L) // 延迟一段时间
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // 取消该任务并且等待它结束
    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!
job: I'm running finally
job: And I've just delayed for 1 sec because I'm non-cancellable
main: Now I can quit.*/
  • withTimeout  withTimeoutOrNull

 你可以设置一个协程的超时时间

fun main(args: Array<String>)= runBlocking<Unit> {
    withTimeout(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
}

/*
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms*/

可以看到,当协程执行超时之后抛出了 TimeoutCancellationException   你可以使用try {}catch(e:TimeoutCancellationException   ){} 去捕获异常并做出处理  也可以使用withTimeoutOrNull 

fun main(args: Array<String>)= runBlocking<Unit> {
    val result = withTimeoutOrNull(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
        "Done" // 在它运行得到结果之前取消它
    }
    println("Result is $result")
}

/*I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Result is null*/

这段代码不会抛异常,result 返回为null

;