Kotlin

Kotlin协程的取消与超时

2021-12-23  本文已影响0人  漆先生

一、取消协程的执行

main 函数调用了 job.cancel ,就可以取消协程。也可以使 Job 挂起的函数 cancelAndJoin 它合并了对 cancel 以及 join 的调用。

fun main() = runBlocking {
    val job = launch {
        repeat(1000) { i ->
            println("job: I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancel() // cancels the job
    job.join() // waits for job's completion
    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.

二、取消是协作的

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

fun main() = runBlocking {
    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.")
}

三、使计算代码可取消

有两种方法来使执行计算的代码可以被取消

fun main() = runBlocking {
    val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (isActive) { // ⼀个执行计算的循环,只是为了占用 CPU
//        while (i < 5) {
//            yield()
            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.")
}

四、在 finally 中释放资源

可被取消的挂起函数,在被取消时抛出 CancellationException ,可用try {……} finally {……} 表达式以及 Kotlin 的 use 函数⼀般在协程被取消的时候执行它们的终结动作:

fun main() = runBlocking {
    val job = launch {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
                delay(500L)
            }
        } catch (e: CancellationException) {
            println("e:${e.message}")
        } finally {
            println("job: I'm running finoally")
        }
    }
    delay(1300L)
    println("main: I'm tired of waiting!")
    job.cancelAndJoin()
    println("main: Now I can quit.")
}

五、运行不能取消的代码块

所有良好的关闭操作(关闭⼀个文件、取消⼀个作 业、或是关闭任何⼀种通信通道)通常都是非阻塞的,并且不会调用任何挂起函数。然而,在真实的案例中,当你 需要挂起⼀个被取消的协程,你可以将相应的代码包装在 withContext(NonCancellable) {……} 中,并 使⽤ withContext 函数以及 NonCancellable 上下文

fun main() = runBlocking {
    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.")
}

六、超时

使用 withTimeout 函数来设置超时时间

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

控制台会输出

Exception in thread "main" kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 1300 ms

withTimeout 抛出了 TimeoutCancellationException ,它是 CancellationException 的子类。我们之前没有在控制台上看到堆栈跟踪信息的打印。这是因为在被取消的协程中 CancellationException 被认为是协程执行结束的正常原因。
如果你需要做⼀些各类使用超时的特别的额外操作,可以使用类似 withTimeout 的 withTimeoutOrNull 函数,并把这些会超时的代码包装在 try {...} catch (e: TimeoutCancellationException) {...} 代码块中,而 withTimeoutOrNull 通过返回 null 来进行超时操作,从而替代抛出⼀个异常:

fun main() = runBlocking {
    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
上一篇下一篇

猜你喜欢

热点阅读