Kotlin学习笔记之 30 协程取消与超时

417 阅读1分钟

首发于公众号: DSGtalk1989

30.协程取消与超时

  • 如何取消我们需要的取消

    上文中出现了cancel方法来进行取消的操作,但是过程中,我们的例子我怕会有误解,我们直接在协程还没有启动的时候取消了它,我们来看下是否可以启动了一会儿还可以暂停呢。这个和我们java中熟知的Thread比较不一样,首先Thread在很久以前就不跟你玩cancel了,其次Thread只要起来了,就停不下来了。不像协程,起都没起,还能够取消。

    我们变一种取消的方式再来看看。

    fun main() = runBlocking {
          val startTime = System.currentTimeMillis()
          val job = launch {
              var nextPrintTime = startTime
              var i = 0
              while (i < 5) { // 一个执行计算的循环,只是为了占用 CPU
                  // 每秒打印消息两次
                  if (System.currentTimeMillis() >= nextPrintTime) {
                      println("I'm sleeping ${i++} ...")
                      nextPrintTime += 500L
                  }
              }
          }
          println("runBlocking start")
          delay(10)
          job.cancel()
          println("delay end")
      }
    

    我们只是delay了10ms,照理来说cancel应该立马让线程停掉,但是依然把launch的协程给完全跑完了。因为我们并没有在协程中去检测现在是否协程还活着。这个跟Thread中的interrupt方法比较像,中断过后线程的信号量会改变,我们需要时刻的去关注线程当前是否被打断了,并且时刻的捕捉InterruptException

    前文中之所以我们可以操作cancel是因为函数delay是支持取消的。

    This suspending function is cancellable.

    所以一旦遇到如上的情况,我们就需要在遍历循环或者是计算的过程中去判断一下isActive这个字段,协程是否还活着。同时调用yield方法也能实现。

  • ####finally语句关闭资源

    针对上面的情况,一旦我们取消了协程,而我们希望协程中使用到的一些资源可以释放掉,我们就使用try finally

    val job = launch {
          try {
              repeat(1000) { i ->
                  println("I'm sleeping $i ...")
                  delay(500L)
              }
          } finally {
              println("I'm running finally")
          }
      }
    
  • ####执行无法取消的代码

    有时候我们不希望我们的部分代码段可以被取消,无论是各种可以取消的挂起函数,还是其他的计算函数。

    val job = launch {
          repeat(1000) { i ->
              println("I'm sleeping $i ...")
              withContext(NonCancellable){
                  delay(500L)
              }
          }
      }
    

    这样一来,delay就无法取消了。

  • 超时异常

    java中,我们通常需要去中断一个线程的最主要的原因就是超时了,这边一样,在kotlin中,为我们提供了一个非常方便的方法withTimeout,一旦超过了规定的时间,就会自动抛出TimeoutCancellationException,这是一个CancellationException的子类,所以他也是需要挂起函数支持被取消才行。

    我们也可以使用withTimeoutOrNull,这个函数一旦超过了指定的时间,会立马返回一个null,而不是抛出异常。

    withTimeout(1300L) {
          repeat(1000) { i ->
              println("I'm sleeping $i ...")
              delay(500L)
          }
      }
      
    val result = withTimeoutOrNull(1300L) {
          repeat(1000) { i ->
              println("I'm sleeping $i ...")
              delay(500L)
          }
          "Done" // 协程会在输出这个消息之前被取消
      }
      println("Result is $result")