前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【Kotlin 协程】协程取消 ② ( CPU 密集型协程任务取消 | 使用 isActive 判定协程状态 | 使用 ensureActive 函数取消协程 | 使用 yield 函数取消协程 )

【Kotlin 协程】协程取消 ② ( CPU 密集型协程任务取消 | 使用 isActive 判定协程状态 | 使用 ensureActive 函数取消协程 | 使用 yield 函数取消协程 )

作者头像
韩曙亮
发布2023-03-30 18:24:13
1K0
发布2023-03-30 18:24:13
举报
文章被收录于专栏:韩曙亮的移动开发专栏

文章目录

一、CPU 密集型协程任务取消


在 协程中 , 定义在 kotlinx.coroutines 包下的 suspend 挂起函数 是可以取消的 ;

但是有一种协程任务 , CPU 密集型协程任务 , 是无法 直接取消的 ; 此类任务一直在 抢占 CPU 资源 , 使用 cancel 函数 , 无法取消该类型的 协程任务 ;

在进行 CPU 密集计算时 , 中间会有大量的中间数据 , 如果中途取消 , 大量的临时数据会丢失 , 因此在协程中 , 无法直接取消 CPU 密集型协程任务 , 这是对协程的保护措施 ;

CPU 密集型协程任务取消示例 : 在下面的 协程任务 中 , 循环 10000000 次 , 100 ms 后取消 ;

代码语言:javascript
复制
package kim.hsl.coroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*

class MainActivity : AppCompatActivity(){
    val TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
            // 创建协程作用域
            val coroutineScope = CoroutineScope(Dispatchers.Default)

            val job1 = coroutineScope.launch {
                Log.i(TAG, "协程任务执行开始")
                var i = 0
                while (i < 10000000) {
                    var j = i + 1
                    i++
                    if(j == 10000000) {
                        Log.i(TAG, "最后一次循环 : j = ${j}")
                        Log.i(TAG, "协程任务执行完毕")
                    }
                }
            }

            // 100ms 后取消协程作用域
            delay(10)

            Log.i(TAG, "取消协程任务")
            // 取消协程任务
            job1.cancelAndJoin()
            Log.i(TAG, "退出协程作用域")
        }
    }
}

执行结果 : 在执行协程任务过程中 , 取消协程 , 但是没有取消成功 , 协程自动执行完毕 ;

代码语言:javascript
复制
18:45:33.896  I  协程任务执行开始
18:45:33.906  I  取消协程任务
18:45:33.997  I  最后一次循环 : j = 10000000
18:45:33.997  I  协程任务执行完毕
18:45:34.001  I  退出协程作用域

二、使用 isActive 判定当前 CPU 密集型协程任务是否取消


协程 处于 活跃 Active 状态 时 , 当调用 Job#cancel 函数取消协程时 , 当前的任务会变为 取消中 Cancelling 状态 ,

取消中 Cancelling 状态 通过 ( isActive == false && isCancelled == true ) 可以进行判定 ;

当所有的子协程执行完毕会后 , 协程会进入 已取消 Cancelled 状态 ,

已取消 Cancelled 状态 通过 ( isCompleted == true ) 进行判定 ;

如果 调用了 Job#cancel 函数 取消协程 , 此时的 isActive 值肯定为 false , 这里在 CPU 密集型协程任务 执行时 , 时刻调用 isActive 判定当前状态即可 ;

如 : 在下面的代码中 , 每次循环都判定一次 isActive 是否为 true , 如果为 false , 则终止循环 , 即终止协程 ;

代码语言:javascript
复制
val job1 = coroutineScope.launch {
    Log.i(TAG, "协程任务执行开始")
    var i = 0
    while (i < 10000000 && isActive) {
        var j = i + 1
        i++
        if(j == 10000000) {
            Log.i(TAG, "最后一次循环 : j = ${j}")
            Log.i(TAG, "协程任务执行完毕")
        }
    }
}

协程声明周期状态 参考 【Kotlin 协程】协程启动 ⑥ ( 协程生命周期状态 | 新创建 New | 活跃 Active | 完成中 Completing | 已完成 Completed | 取消中 | 已取消 )

代码示例 : 在下面的代码中 , 执行 CPU 密集型任务 , 循环 10000000 次进行运算 , 然后在每次循环时 , 都调用 isActive 判定当前的协程是否被取消 ;

代码语言:javascript
复制
package kim.hsl.coroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*

class MainActivity : AppCompatActivity(){
    val TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
            // 创建协程作用域
            val coroutineScope = CoroutineScope(Dispatchers.Default)

            val job1 = coroutineScope.launch {
                Log.i(TAG, "协程任务执行开始")
                var i = 0
                while (i < 10000000 && isActive) {
                    var j = i + 1
                    i++
                    if(j == 10000000) {
                        Log.i(TAG, "最后一次循环 : j = ${j}")
                        Log.i(TAG, "协程任务执行完毕")
                    }
                }
            }

            // 100ms 后取消协程作用域
            delay(10)

            Log.i(TAG, "取消协程任务")
            // 取消协程任务
            job1.cancelAndJoin()
            Log.i(TAG, "退出协程作用域")
        }
    }
}

执行结果 :

代码语言:javascript
复制
19:44:23.632  I  协程任务执行开始
19:44:23.675  I  取消协程任务
19:44:23.680  I  退出协程作用域

三、使用 ensureActive 自动处理协程退出


在协程中 , 可以执行 ensureActive() 函数 , 在该函数中会 自自动判定当前的 isActive 状态 , 如果当前处于取消中状态 , 自动抛出 CancellationException 异常 , 并退出协程 ;

代码语言:javascript
复制
/**
 * 确保当前作用域是[活动的][CoroutineScope.isActive]。
 *
 * 如果作业不再活动,则抛出[CancellationException]。
 * 如果作业被取消,则抛出异常包含原始的取消原因。
 * 如果作用域的[coroutineContext][CoroutineScope.coroutineContext]中没有[Job],则此函数不做任何事情。
 *
 * 这个方法可以替代以下代码,但有更精确的例外:
 * ```
 * if (!isActive) {
 *     throw CancellationException()
 * }
 * ```
 *
 * @see CoroutineContext.ensureActive
 */
public fun CoroutineScope.ensureActive(): Unit = coroutineContext.ensureActive()

其真实操作如下 :

代码语言:javascript
复制
public fun Job.ensureActive(): Unit {
    if (!isActive) throw getCancellationException()
}

核心代码示例 : 协程中执行的循环任务 , 每次循环时 , 都调用一次 ensureActive() 函数 , 判断当前协程是否已经取消 , 如果已经取消则抛出异常 , 退出协程 ;

代码语言:javascript
复制
val job1 = coroutineScope.launch {
    Log.i(TAG, "协程任务执行开始")
    var i = 0
    while (i < 10000000) {
        ensureActive()
        var j = i + 1
        i++
    }
}

完整代码示例 :

代码语言:javascript
复制
package kim.hsl.coroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext

class MainActivity : AppCompatActivity(){
    val TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
            // 创建协程作用域
            val coroutineScope = CoroutineScope(Dispatchers.Default)

            val job1 = coroutineScope.launch {
                Log.i(TAG, "协程任务执行开始")
                var i = 0
                while (i < 10000000) {
                    ensureActive()
                    var j = i + 1
                    i++
                    if(j == 10000000) {
                        Log.i(TAG, "最后一次循环 : j = ${j}")
                        Log.i(TAG, "协程任务执行完毕")
                    }
                }
            }

            // 100ms 后取消协程作用域
            delay(10)

            Log.i(TAG, "取消协程任务")
            // 取消协程任务
            job1.cancelAndJoin()
            Log.i(TAG, "退出协程作用域")
        }
    }
}

执行结果 :

代码语言:javascript
复制
19:44:23.632  I  协程任务执行开始
19:44:23.675  I  取消协程任务
19:44:23.680  I  退出协程作用域

四、使用 yield 函数检查协程状态并处理协程取消操作


在协程中 , 可以使用 yield() 函数 , 检查当前协程的状态 , 如果已经调用 cancel() 函数取消协程 , 则抛出 CancellationException 异常 , 取消协程 ;

yield() 函数 比 ensureActive 函数 更加复杂 , 该函数还尝试出让线程执行权 , 将执行权让给别的协程执行 ; yield() 函数 会在每次循环时 , 都执行一次 , 每次循环时都执行该函数的时候 , 此时会尝试出让线程的执行权 , 看看是否有其它更紧急的协程需要执行 , 如果有 , 则让其它协程先执行 ;

yield() 函数 每次执行前都问一下其它协程 , 你们需要执行吗 , 如果需要先让你们执行一次 ;

这样可以避免 协程的 CPU 占用太密集 , 导致其它协程无法执行 的情况 ;

yield() 函数原型 :

代码语言:javascript
复制
/**
 * 生成当前协程分配器的线程(或线程池)
 * 到同一调度程序上运行的其他协程。
 *
 * 这个暂停功能是可以取消的。
 * 如果在调用此挂起函数时取消或完成当前协程的[Job]
 * 这个函数正在等待调度,它会以[CancellationException]恢复。
 * 有**立即取消的保证**。如果在此函数被取消时作业被取消
 * 挂起后,它将无法成功恢复。有关底层细节,请参阅[suspendCancellableCoroutine]文档。
 *
 * **注意**:这个函数总是[检查取消][ensureActive],即使它没有挂起。
 *
 * ###实现细节
 *
 * 如果协程调度程序为[unrestricted][Dispatchers.]无侧限),这
 * 函数仅在有其他无限制协程工作并形成事件循环时才挂起。
 * 对于其他调度程序,该函数调用[CoroutineDispatcher]。调度),
 * 无论[CoroutineDispatcher.isDispatchNeeded]的结果如何,总是挂起以便稍后恢复。
 * 如果上下文中没有[CoroutineDispatcher],它就不会挂起。
 */
public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
    val context = uCont.context
    context.ensureActive()
    val cont = uCont.intercepted() as? DispatchedContinuation<Unit> ?: return@sc Unit
    if (cont.dispatcher.isDispatchNeeded(context)) {
        // 这是一个常规的分派器——执行简单的dispatchYield
        cont.dispatchYield(context, Unit)
    } else {
        // 这要么是“即时”调度程序,要么是无限制调度程序
        // 此代码检测unrestricted调度程序,即使它被包装到另一个调度程序中
        val yieldContext = YieldContext()
        cont.dispatchYield(context + yieldContext, Unit)
        // 仅能在已有的无约束循环中屈服的无约束调度程序的特殊情况
        if (yieldContext.dispatcherWasUnconfined) {
            // 说明无限制调度员接到了电话,但什么都没做。
            // 参见“无限制”代码。调度”功能。
            return@sc if (cont.yieldUndispatched()) COROUTINE_SUSPENDED else Unit
        }
        // 否则,就是其他调度程序成功地调度了协程
    }
    COROUTINE_SUSPENDED
}

完整代码示例 :

代码语言:javascript
复制
package kim.hsl.coroutine

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
import kotlin.coroutines.intrinsics.intercepted
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn

class MainActivity : AppCompatActivity(){
    val TAG = "MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
            // 创建协程作用域
            val coroutineScope = CoroutineScope(Dispatchers.Default)

            val job1 = coroutineScope.launch {
                Log.i(TAG, "协程任务执行开始")
                var i = 0
                while (i < 10000000) {
                    yield()
                    var j = i + 1
                    i++
                    if(j == 10000000) {
                        Log.i(TAG, "最后一次循环 : j = ${j}")
                        Log.i(TAG, "协程任务执行完毕")
                    }
                }
            }

            // 100ms 后取消协程作用域
            delay(10)

            Log.i(TAG, "取消协程任务")
            // 取消协程任务
            job1.cancelAndJoin()
            Log.i(TAG, "退出协程作用域")
        }
    }
}

执行结果 :

代码语言:javascript
复制
20:20:59.008  I  协程任务执行开始
20:20:59.055  I  取消协程任务
20:20:59.059  I  退出协程作用域
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-11-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 文章目录
  • 一、CPU 密集型协程任务取消
  • 二、使用 isActive 判定当前 CPU 密集型协程任务是否取消
  • 三、使用 ensureActive 自动处理协程退出
  • 四、使用 yield 函数检查协程状态并处理协程取消操作
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档