協程入門(二):掛起與取消

協程的掛起與線程的休眠

協程經過delay(timeMillis)實現掛起,線程經過sleep(timeMillis)實現休眠。可是掛起和休眠存着差別性api

協程掛起與線程休眠bash

相同點:微信

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

2.在該狀態下(掛起/休眠)都能被終止執行(取消/中斷);

3.取消/中斷時都會拋出異常
複製代碼

不一樣點:async

1.線程休眠會直接堵塞當前線程,該線程沒法再執行其它操做,可是協程掛起不會堵塞當前線程,線程上的其它協程能夠繼續運行;

2.delay操做只能在協程環境使用,sleep在協程環境和普通線程環境均可使用

3.線程中斷拋出的是InterruptedException異常,協程中斷拋出的是CancellationException異常
複製代碼

協程的取消與線程的中斷

協程的取消與線程的中斷,2者十分相似 :spa

比較 調用api 能被直接取消的狀態 事務處理時 拋出的異常
協程 cancel() delay 沒法被直接取消 CancellationException
線程 interrupt() sleep 沒法被直接取消 InterruptedException

相同點:線程

1.都是須要處於掛起/休眠狀態,纔可以直接取消/中斷;code

2.處於事務處理時,沒法直接被取消/中斷;cdn

不一樣點在於:協程

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
複製代碼

微信公衆號

相關文章
相關標籤/搜索