协程入门(二):挂起与取消

3,822 阅读3分钟

协程的挂起与线程的休眠

协程通过delay(timeMillis)实现挂起,线程通过sleep(timeMillis)实现休眠。但是挂起和休眠存着差异性

协程挂起与线程休眠

相同点:

1.都能达到堵塞的目的;

2.在该状态下(挂起/休眠)都能被终止执行(取消/中断);

3.取消/中断时都会抛出异常

不同点:

1.线程休眠会直接堵塞当前线程,该线程无法再执行其它操作,但是协程挂起不会堵塞当前线程,线程上的其它协程可以继续运行;

2.delay操作只能在协程环境使用,sleep在协程环境和普通线程环境都可使用

3.线程中断抛出的是InterruptedException异常,协程中断抛出的是CancellationException异常

协程的取消与线程的中断

协程的取消与线程的中断,2者十分类似 :

比较 调用api 能被直接取消的状态 事务处理时 抛出的异常
协程 cancel() delay 无法被直接取消 CancellationException
线程 interrupt() sleep 无法被直接取消 InterruptedException

相同点:

1.都是需要处于挂起/休眠状态,才能够直接取消/中断;

2.处于事务处理时,无法直接被取消/中断;

不同点在于:

1.协程存在父协程的概念,但是线程没有啥所谓的父线程。取消父协程后,会自动取消其所有的子协程;

2.协程在挂起时被取消,会抛出CancellationException异常,线程在休眠时被中断,会抛出InterruptedException

示例

能被直接取消/中断

该协程job在执行cancel的时候,处于delay状态,能够被直接取消

    fun cancel() = runBlocking {
        val job = launch {
            println("1")
            delay(3000)
            //被cancel后,下边不再执行
            println("2")
        }
        delay(100)
        job.cancel()
        println("3")
    }
    
打印出:
    1
    3

该线程thread在执行interrupt的时候,处于sleep状态,能够被直接中断

    fun threadInterrupt() {
        val thread = thread {
            println("1")
            Thread.sleep(1 * 1000)
            println("2")
        }
        //抛出InterruptedException但不影响后面执行
        thread.interrupt()
        Thread.sleep(3 * 1000)
        println("3")
    }
打印出:
1
3

处于事务处理,无法被直接取消/中断

协程在执行cancel()后,内部的扩展属性isActive会置为false,代码中可以结合该变量跳出事务处理的流程。 如下例子中的while()判断,假设没有结合iaActive,因为当前协程处于事务处理,不是delay挂起状态,无法直接被cancel, 需要等待事务处理完毕。

为了达到cancel后尽早结束协程,可以结合isActive进行判断。

    fun cancel() = runBlocking {
        val job = launch(Dispatchers.Default) {
            var nextPrintTime = System.currentTimeMillis()
            var i = 0
            //isActive是协程扩展属性,cancel()后变为false
            while (i < 5 && isActive) {
                // 一个执行计算的循环,只是为了占用 CPU
                if (System.currentTimeMillis() >= nextPrintTime) {
                    println("job: I'm sleeping ${i++} ...")
                    // 每秒打印消息两次
                    nextPrintTime += 500L
                }
            }
        }
        delay(100)
        job.cancel()
        println("end")
    }
    
结合isActive进行中断判断,打印出:
job: I'm sleeping 0 ...
end

假设没有结合isActive进行判断,打印出:
job: I'm sleeping 0 ...
end
job: I'm sleeping 1 ...
job: I'm sleeping 2 ...
job: I'm sleeping 3 ...
job: I'm sleeping 4 ...

线程使用interrupt()结合isInterrupted变量,达到终端操作的目的,效果与分析同协程cancel()结合isActive类似:

    fun threadInterrupt() {
        val thread = thread {
            var nextPrintTime = System.currentTimeMillis()
            var i = 0
            //isInterrupted是线程属性,interrupt()后变为false
            while (i < 5 && !Thread.currentThread().isInterrupted) {
                // 一个执行计算的循环,只是为了占用 CPU
                if (System.currentTimeMillis() >= nextPrintTime) {
                    println("thread: I'm sleeping ${i++} ...")
                    // 每秒打印消息两次
                    nextPrintTime += 500L
                }
            }
        }
        thread.interrupt()
        Thread.sleep(3 * 1000)
        println("end")
    }
    
结合isInterrupted进行中断判断,打印出:
thread: I'm sleeping 0 ...
end

假设没有结合isInterrupted进行判断,打印出:
thread: I'm sleeping 0 ...
end
thread: I'm sleeping 1 ...
thread: I'm sleeping 2 ...
thread: I'm sleeping 3 ...
thread: I'm sleeping 4 ...

取消父协程会导致子协程也跟着取消

父协程手动调用cancel()或者异常结束,会立即取消它的所有子协程。

下例cancel()父协程job,其neibu内部的子协程都会跟着也cancel掉。

    fun cancel() = runBlocking {
        val job = launch {
            launch(Dispatchers.Default) {
                var nextPrintTime = System.currentTimeMillis()
                var i = 0
                //isActive是协程扩展属性,cancel()后变为false
                while (i < 5 && isActive) {
                    // 一个执行计算的循环,只是为了占用 CPU
                    if (System.currentTimeMillis() >= nextPrintTime) {
                        println("child launch: I'm sleeping ${i++} ...")
                        // 每秒打印消息两次
                        nextPrintTime += 500L
                    }
                }
            }

            async(Dispatchers.IO) {
                var nextPrintTime = System.currentTimeMillis()
                var i = 0
                //isActive是协程扩展属性,cancel()后变为false
                while (i < 5 && isActive) {
                    // 一个执行计算的循环,只是为了占用 CPU
                    if (System.currentTimeMillis() >= nextPrintTime) {
                        println("child async: I'm sleeping ${i++} ...")
                        // 每秒打印消息两次
                        nextPrintTime += 500L
                    }
                }
            }
        }
        delay(1000)
        job.cancel()
        println("cancel parent job")
        delay(10 * 1000)
    }
    
打印出:
child launch: I'm sleeping 0 ...
child async: I'm sleeping 0 ...
cancel parent job

微信公众号