Kotlin中协程的取消与取消的副作用以及超时任务

本文详细介绍了Kotlin协程的取消机制,包括取消作用域、兄弟协程不受影响、使用isActive、ensureActive和yield检测及响应取消。同时,讨论了取消的副作用,如在finally块中释放资源,并展示了如何在CPU密集型任务中优雅地取消。此外,还讲解了不能取消的任务情况以及如何处理超时任务,包括withTimeoutOrNull函数的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Kotlin中协程的取消与取消的副作用以及超时任务


协程取消

  • 取消作用域会取消它的子协程。
  • 被取消的子协程并不会影响其余兄弟协程。
  • 协程通过抛出一个特殊的异常CancellationException来处理取消操作。
  • 所有kotlinx.coroutines中的挂起函数(withContext、delay等)都是可以取消的。
@Test
    fun `test scope cancel`() = runBlocking<Unit> {
        val scope = CoroutineScope(Dispatchers.Default)
        scope.launch {
            delay(1000)
            println("job 1")
        }
        scope.launch {
            delay(1000)
            println("job 2")
        }
        delay(1000)
        scope.cancel() //取消作用域
        //runBlocking 协程等待子协程运行
        delay(2000)
    }

    @Test
    fun `test brother cancel`() = runBlocking<Unit> {
        val scope = CoroutineScope(Dispatchers.Default)
        val job1 = scope.launch {
            delay(1000)
            println("job 1")
        }
        val job2 = scope.launch {
            delay(1000)
            println("job 2")
        }
        delay(100)
        job1.cancel() //取消作用域 ,job2 不影响
        //runBlocking 协程等待子协程运行
        delay(2000)
    }

    @Test
    fun `test CancellationException`() = runBlocking<Unit> {
        val job1 = GlobalScope.launch {
            try {
                delay(1000)
                println("job 1")
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
        delay(100)
//        job1.cancel(CancellationException("取消协程"))
//        job1.join()
        job1.cancelAndJoin()
    }

CPU密集型任务取消

  • 1.isActive是一个可以被使用在CoroutioneScope中的扩展属性,检查Job是否处于活跃状态。
  • 2.ensureActive(),如果job处于非活跃状态,这个方法会立即抛出异常。
  • 3.yield函数会检查所在协程的状态,如果已经取消,则抛出CancellationException予以响应。 此外它还会尝试出让线程的执行权,给其他协程提供执行机会。
@Test
    fun `test cancel cpu task by isActive`() = runBlocking<Unit> {
        val startTime = System.currentTimeMillis()
        val job = launch(Dispatchers.Default) {
            var nextPrintTime = startTime
            var i = 0
            while (i < 5 && isActive) { //isActive 判断cancel是否被调用,没有被调用为true
                if (System.currentTimeMillis() >= nextPrintTime) {
                    println("job: I'm sleeping")
                    nextPrintTime += 500
                }
            }
        }
        delay(1300)
        println("main: I'm tired of waiting")
        job.cancelAndJoin()
        println("main: Now I can quit")
    }

    @Test
    fun `test cancel cpu task by ensureActive`() = runBlocking<Unit> {
        val startTime = System.currentTimeMillis()
        val job = launch(Dispatchers.Default) {
            var nextPrintTime = startTime
            var i = 0
            while (i < 5) {
                ensureActive() //退出子协程会抛出异常,异常被静默处理,可以手动捕获
                if (System.currentTimeMillis() >= nextPrintTime) {
                    println("job: I'm sleeping")
                    nextPrintTime += 500
                }
            }
        }
        delay(1300)
        println("main: I'm tired of waiting")
        job.cancelAndJoin()
        println("main: Now I can quit")
    }

    @Test
    fun `test cancel cpu task by yield`() = runBlocking<Unit> {
        val startTime = System.currentTimeMillis()
        val job = launch(Dispatchers.Default) {
            var nextPrintTime = startTime
            var i = 0
            while (i < 5) {
                yield() //退出子协程会抛出异常,异常被静默处理,可以手动捕获
                if (System.currentTimeMillis() >= nextPrintTime) {
                    println("job: I'm sleeping")
                    nextPrintTime += 500
                }
            }
        }
        delay(1300)
        println("main: I'm tired of waiting")
        job.cancelAndJoin()
        println("main: Now I can quit")
    }

协程取消的副作用

  • 1.在finally中释放资源。
  • 2.use函数:该函数只能被实现了Closeable的对象使用,程序结束的时候会自动调用close方法,适合文件对象。
@Test
    fun `test crelease resources`() = runBlocking<Unit> {
        var job = launch {
            try {
                repeat(1000) { i ->
                    println("job: I'm sleeping $i")
                    delay(500L)
                }
            } finally { //释放资源
                println("job: I'm running finally")
            }

        }
        delay(1300)
        println("main: I'm tired of waiting")
        job.cancelAndJoin()
        println("main: Now I can quit")
    }

    @Test
    fun `test use function`() = runBlocking<Unit> {
        var br = BufferedReader(FileReader("D:\\a.txt"))
        with(br) {
            var line: String?
            try {
                while (true) {
                    line = readLine() ?: break;
                    println(line)
                }
            } finally {
                close()
            }
        }
        //use函数自动调用close方法 关闭释放资源
        BufferedReader(FileReader("D:\\a.txt")).use {
            var line: String?
            while (true) {
                line = it.readLine() ?: break;
                println(line)
            }
        }
    }

不能取消的任务

  • 1.处于取消中状态的协程不能够挂起(运行不能取消的代码),当协程被取消后需要调用挂起函数,我们需要将清理任务的代码放置于NonCancellable CoroutineContext中。
  • 2.这样会挂起运行中的代码,并保持协程的取消中状态直到任务处理完成。
  @Test
    fun `test cancel with NonCancellable`() = runBlocking<Unit> {
        var job = launch {
            try {
                repeat(1000) { i ->
                    println("job: I'm sleeping $i")
                    delay(500L)
                }
            } finally { //释放资源
                //协程取消时使用withContext来不影响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(1300)
        println("main: I'm tired of waiting")
        job.cancelAndJoin()
        println("main: Now I can quit")
    }

超时任务

  • 1.很多情况下取消一个协程的理由是它有可能超时。
  • 2.withTimeoutOrNull通过返回null来进行超时操作,从而代替抛出一个异常。
 @Test
    fun `test deal with timeout`() = runBlocking<Unit> {
        withTimeout(1300){
            repeat(1000){ i->
                println("job: I'm sleeping $i")
                delay(500L)
            }
        }
    }
    @Test
    fun `test deal with timeout return null`() = runBlocking<Unit> {
       val result =  withTimeoutOrNull(1300){
            repeat(1000){ i->
                println("job: I'm sleeping $i")
                delay(500L)
            }
           "Done"
        } ?:"yuknight"
        println("Result is $result")
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yu-Knight

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值