学习协程2: 取消和超时

这篇具有很好参考价值的文章主要介绍了学习协程2: 取消和超时。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Kotlin协程官方网址

Cancelling coroutine execution

对于长时间运行的程序,需要进行粒度控制,在合适的时间结束协程。launch返回一个job对象,可以使用该对象取消正在运行的协程。取消是抛出 CancellationException,如果不捕捉,协程被取消。

fun cancelCoroutine() = 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.")

}

// output
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.

开启一个协程开始打印,没打印一次延时500ms,整个线程延时1300ms, 因此打印三次。然后取消协程,等待协程结束后打印最后的语句。

Cancellation is cooperative

取消是协作的,一个协程可以被取消必须是与其他协作。kotlinx.coroutines中的挂起函数都是可取消的。但是,如果协程在计算中工作并且不检查取消,则无法取消。如下:


val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 5) { // computation loop, just wastes CPU
            // print a message twice a second
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
//output
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
job: I'm sleeping 3 ...
job: I'm sleeping 4 ...
main: Now I can quit.

同样的问题可以观察到通过捕捉 CancellationException

val job = launch(Dispatchers.Default) {
        repeat(5) { i ->
            try {
                // print a message twice a second
                println("job: I'm sleeping $i ...")
                delay(500)
            } catch (e: Exception) {
                // log the exception
                println(e)
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
// output
job: I'm sleeping 0 ...
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
main: I'm tired of waiting!
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@23ff831c
job: I'm sleeping 3 ...
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@23ff831c
job: I'm sleeping 4 ...
kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}@23ff831c
main: Now I can quit.

在取消协程是会抛出 CancellationException,但是,如果协程在计算中工作并且不检查取消,则无法取消。

Making computation code cancellable

两种方法可以取消计算工作的协程。

  1. 第一种方法是定期调用检查取消的挂起函数。有一个yield函数是一个很好的选择。
  2. 另一个是显式检查取消状态。

use yield

如果要处理的任务属于 1) CPU 密集型,2) 可能会耗尽线程池资源,3) 需要在不向线程池中添加更多线程的前提下允许线程处理其他任务,那么请使用 yield()。如果 job 已经完成,由 yield 所处理的首要任务将会是检查任务的完成状态,完成的话则直接通过抛出 CancellationException 来退出协程。yield 可以作为定期检查所调用的第一个函数

val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 5) { // computation loop, just wastes CPU
            // print a message twice a second
            yield()
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
// out put
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.

explicitly check the cancellation status

val startTime = System.currentTimeMillis()
    val job = launch(Dispatchers.Default) {
        var nextPrintTime = startTime
        var i = 0
        while (i < 5 && isActive) { // computation loop, just wastes CPU
            // print a message twice a second
            if (System.currentTimeMillis() >= nextPrintTime) {
                println("job: I'm sleeping ${i++} ...")
                nextPrintTime += 500L
            }
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
//output
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.

Closing resources with finally

可取消挂起函数在取消时抛出CancellationException,这可以用通常的方式处理。例如,try{…} finally{…}表达式和Kotlin的use函数在协程被取消时正常执行它们的结束动作。

val job = launch {
        try {
            repeat(1000) { i ->
                println("job: I'm sleeping $i ...")
                delay(500L)
            }
        } finally {
            println("job: I'm running finally")
        }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
//output

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.

Run non-cancellable block

finally中使用任何挂起函数都会造成 CancellationException, 因为协程已经被取消了。如果需要在取消的协程中挂起,使用withContext(NonCancellable) {...}使用 withContext 函数和NonCancellable context

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) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancelAndJoin() // cancels the job and waits for its completion
    println("main: Now I can quit.")
// output
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.

Timeout

使用 withTimeout在超时后取消协程。

withTimeout(1300L) {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
//output
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

withTimeout抛出的TimeoutCancellationExceptionCancellationException的子类。以前没有在控制台上看到它的堆栈跟踪。这是因为在取消的协程中,CancellationException被认为是协程完成的正常原因。然而,在这个例子中,在main函数中使用了withTimeout。由于取消只是一个异常,所以所有资源都以通常的方式关闭。可以在try{…} catch (e: TimeoutCancellationException){…}块,如果需要在任何类型的超时时执行一些额外的操作,或者使用与withTimeout类似的withTimeoutOrNull函数,但它在超时时返回null,而不是抛出异常。

    try {
        withTimeout(1300L) {
            repeat(1000) { i ->
                println("I'm sleeping $i ...")
                delay(500L)
            }
        }
    } catch (e:TimeoutCancellationException) {
        println(e.message)
    }
    println("this is end")
// output
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
Timed out waiting for 1300 ms
this is end

val result = withTimeoutOrNull(1300L) {
    repeat(1000) { i ->
        println("I'm sleeping $i ...")
        delay(500L)
    }
    "Done" // will get cancelled before it produces this result
}
println("Result is $result")

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

Asynchronous timeout and resources

withTimeout中的超时事件相对于在其块中运行的代码是异步的,并且可以在任何时间发生,甚至在从超时块内部返回之前发生。如果在块内部打开或获取一些需要在块外部关闭或释放的资源,请记住这一点。例如,这里我们用resource类模拟一个可关闭的资源,它只是通过增加获取的计数器和减少其close函数中的计数器来跟踪它被创建的次数。现在让我们创建许多协程,每个协程在withTimeout块的末尾创建一个Resource,并在块外部释放资源。我们添加了一个小延迟,以便在withTimeout块已经完成时更有可能发生超时,这将导致资源泄漏。

var acquired = 0

class Resource {
    init { acquired++ } // Acquire the resource
    fun close() { acquired-- } // Release the resource
}

fun main() {
    runBlocking {
        repeat(10_000) { // Launch 10K coroutines
            launch { 
                val resource = withTimeout(60) { // Timeout of 60 ms
                    delay(50) // Delay for 50 ms
                    Resource() // Acquire a resource and return it from withTimeout block     
                }
                resource.close() // Release the resource
            }
        }
    }
    // Outside of runBlocking all coroutines have completed
    println(acquired) // Print the number of resources still acquired
}
//output
1103

要解决这个问题,可以将对资源的引用存储在变量中,而不是从withTimeout块返回它。文章来源地址https://www.toymoban.com/news/detail-442041.html

runBlocking {
    repeat(10_000) { // Launch 10K coroutines
        launch { 
            var resource: Resource? = null // Not acquired yet
            try {
                withTimeout(60) { // Timeout of 60 ms
                    delay(50) // Delay for 50 ms
                    resource = Resource() // Store a resource to the variable if acquired      
                }
                // We can do something else with the resource here
            } finally {  
                resource?.close() // Release the resource if it was acquired
            }
        }
    }
}
// Outside of runBlocking all coroutines have completed
println(acquired) // Print the number of resources still acquired
//output 
0

到了这里,关于学习协程2: 取消和超时的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Kotlin 协程一 —— 协程 Coroutine

    1.1.1基本定义 进程 进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程,是操作系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。 进程是资源分配的最小单位,在单核CPU中,同一时刻只有一个程序在内存中被CPU调用运行。 线程 基本的

    2024年02月05日
    浏览(38)
  • kotlin语法进阶 - 协程(一)协程基础

    协程并不是一个新的概念,而是一个非常老的概念,很多语言都支持协程,建议去浏览器去了解一下协程的历史和基本概念,这里我们只讲一下kotlin中的协程的作用。 从代码实现角度来看:kotlin协程底层是用线程实现的,是一个封装完善供开发者使用的线程框架。kotlin的一个

    2024年02月09日
    浏览(35)
  • python语言的官网网址,python官方网站网址

    大家好,小编来为大家解答以下问题,python语言的官网网址,python官方网站网址,今天让我们一起来看看吧! Python官网是:https://www.python.org/     官网首页介绍了关于Python的一些信息,下载,文档,社区,成功的故事,新闻和活动。     Python被广泛使用Python Turtle绘制树。

    2024年04月12日
    浏览(35)
  • Kotlin协程-从一到多

    上一篇文章,我介绍了Kotlin协程的创建,使用,协作等内容。本篇将引入更多的使用场景,继续带你走进协程世界。 常用编程语言都会内置对同一类型不同对象的数据集表示,我们通常称之为容器类。不同的容器类适用于不同的使用场景。Kotlin的 Flow 就是在异步计算的需求下

    2024年02月09日
    浏览(45)
  • Kotlin协程-从理论到实战

    上一篇文章从理论上对Kotlin协程进行了部分说明,本文将在上一篇的基础上,从实战出发,继续协程之旅。 在Kotlin中,要想使用协程,首先需要使用协程创建器创建,但还有个前提——协程作用域( CoroutineScope )。在早期的Kotlin实现中,协程创建器是一等函数,也就是说我们随

    2024年02月09日
    浏览(31)
  • Kotlin协程学习之-02

    协程的基本使用 GlobalScope.launch 生命周期与进程一致,且无法取消 runBlocking 会阻塞线程,一般在测试阶段可以使用 val coroutineScope = CoroutineScope(context) coroutineScope.launch 通过context参数去管理和控制协程的生命周期 用法 val coroutineScope = CoroutineScope(context) coroutineScope.launch(Dispatche

    2024年01月22日
    浏览(34)
  • Kotlin 协程 - 多路复用 select()

            又叫选择表达式,是一个挂起函数,可以同时等待多个挂起结果,只取用最快恢复的那个值(即多种方式获取数据,哪个更快返回结果就用哪个)。         同时到达 select() 会优先选择先写子表达式,想随机(公平)的话使用 selectUnbiased() 替换 。         能

    2024年02月10日
    浏览(37)
  • Android Kotlin 协程初探

    维基百科:协程,英文Coroutine [kəru’tin] (可入厅),是计算机程序的一类组件,推广了协作式多任务的子程序,允许执行被挂起与被恢复。 作为Google钦定的Android开发首选语言Kotlin,协程并不是 Kotlin 提出来的新概念,目前有协程概念的编程语言有Lua语言、Python语言、Go语言

    2024年02月08日
    浏览(32)
  • 协程 VS 线程,Kotlin技术精讲

    协程(coroutines)是一种并发设计模式,您可以在Android 平台上使用它来简化异步执行的代码。协程是在版本 1.3 中添加到 Kotlin 的,它基于来自其他语言的既定概念。 在 Android 上,协程有助于管理长时间运行的任务,如果管理不当,这些任务可能会阻塞主线程并导致应用无响应。

    2024年02月09日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包