協程入門(五):異常處理

關鍵點

1.子協程未被捕獲的異常會拋出到父協程,若是父協程沒捕獲異常,會致使父協程終止java

2.launch/actor內發生的未被捕獲的異常不會傳遞到父協程以外的地方android

3.async/produce內發生的未被捕獲的異常會傳遞到啓動其父協程所在的線程bash

4.android原生的try catch沒法捕獲到不一樣線程拋出的異常微信

異常的傳播

在協程中,不一樣的啓動方式,對異常的傳播處理不同。async

對於launch和actor構建器是不傳播異常的,async和produce是傳播異常的。 所謂的傳播異常,是指可以將異常主動往外拋到啓動頂層協程所在的線程; 傳播異常表示不在本協程所在線程發生,異常直接往外拋到啓動該協程所在的線程。spa

異常傳播示例

不傳播異常

globalScopeLaunch方式啓動的是頂層協程,自己不存在父協程,在裏面發生異常後, 只會再logCat輸出異常異常,並不會影響到外部線程的運行。線程

fun globalScopeLaunch()  {
        println("start")
        GlobalScope.launch {
            println("launch Throwing exception")
            throw NullPointerException()
        }
        Thread.sleep(3000)
        //GlobalScope.launch產生的異常不影響該線程執行
        println("end")
    }

打印出:
    start
    launch Throwing exception
    Exception in thread "DefaultDispatcher-worker-1 @coroutine#1" java.lang.NullPointerException.....
    end
複製代碼

globalScopeLaunch方式啓動的是頂層協程,自己不存在父協程,在裏面發生異常後, 並不會影響到外部協程的運行,不管是否運行在同一個線程。code

fun globalScopeLaunch()  = runBlocking {
        println("start ${Thread.currentThread().id}")
        GlobalScope.launch {
            println("launch Throwing exception ${Thread.currentThread().id}")
            throw NullPointerException()
        }.join()

        GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) {
            println("launch UNDISPATCHED Throwing exception ${Thread.currentThread().id}")
            throw IndexOutOfBoundsException()
        }.join()

        //GlobalScope.launch產生的異常不影響該協程執行
        println("end ${Thread.currentThread().id}")
    }
    
打印出:
    start 1
    launch Throwing exception 11
    Exception in thread "DefaultDispatcher-worker-1 @coroutine#2" java.lang.NullPointerException...  異常打印
    launch UNDISPATCHED Throwing exception 1
    Exception in thread "main @coroutine#1" java.lang.IndexOutOfBoundsException...  異常打印
    end 1
複製代碼

傳播異常

async內發生的異常能夠被拋出到啓動該協程所在的線程上,可是須要顯示使用await()方法才能夠捕獲到該異常cdn

fun globalScopeAsync()  = runBlocking {
        println("start ${Thread.currentThread().id}")
        val deferred = GlobalScope.async {
            println("async Throwing exception ${Thread.currentThread().id}")
            throw NullPointerException()
        }
        //deferred.join()
        deferred.await()
        //join不會接收異常能夠正常執行下面步驟,await會接收到拋出的異常致使後面步驟沒法執行
        //不使用join和await則能夠正常執行下面步驟
        println("end ${Thread.currentThread().id}")
        delay(3000)
    }
    
使用join打印出:
     start 1
     async Throwing exception 11
     end 1
     
使用await打印出:
     start 1
     async Throwing exception 12
     java.lang.NullPointerException....拋出的異常
     
不使用join和await打印出:
     start 1
     end 1
     async Throwing exception 11
複製代碼

也就意味着不在協程環境下啓動async後,其內部拋出的異常沒法被外部線程捕獲,由於await須要在協程環境下才能調用協程

fun globalScopeAsync()  {
        println("start")
        val deferred = GlobalScope.async {
            println("async Throwing exception")
            throw NullPointerException()
        }
        Thread.sleep(3000)
        //非協程環境沒法使用await,上邊產生的異常不影響該線程執行
        println("end")
    }
    
打印出:
    start
    async Throwing exception
    end
複製代碼

子協程未捕獲的異常會致使父協程終止

子協程(不管是否與父協程在同一線程,async或launch啓動)未被捕獲的異常都會傳播到父協程,致使父協程終止

fun launchChildException()  = runBlocking {
        println("start ${Thread.currentThread().id}")
        //這邊換成async也是同樣結果
        launch {
            println("launch Throwing exception ${Thread.currentThread().id}")
            throw NullPointerException()
        }
        delay(3000)
        //由於子協程出現異常,默認會致使父協程終止,下邊不會被執行
        println("end ${Thread.currentThread().id}")
    }
    
打印出:
    start 1
    launch Throwing exception 1
    java.lang.NullPointerException....
複製代碼
fun launchChildExceptionIO()  = runBlocking {
        println("start ${Thread.currentThread().id}")
        //這邊換成async也是同樣結果
        launch(Dispatchers.IO) {
            println("launch Throwing exception ${Thread.currentThread().id}")
            throw NullPointerException()
        }
        delay(3000)
        //由於子協程出現異常,默認會致使父協程終止,下邊不會被執行
        println("end ${Thread.currentThread().id}")
    }
    
打印出:
   start 1
   launch Throwing exception 11
   java.lang.NullPointerException....
複製代碼

異常捕獲示例

使用try{}catch{}捕獲

對deferred.await()進行try catch能正常捕獲到錯誤, 可是對join try catch等同於沒有。 這也意味着只有async的中發生的異常,才存在被try catch的可能。

fun asyncCatchException()  = runBlocking {
        println("start ${Thread.currentThread().id}")
        val deferred =  GlobalScope.async {
            println("async Throwing exception ${Thread.currentThread().id}")
            throw NullPointerException()
        }
        try {
            //await可以被try catch捕獲錯誤,從而繼續玩下執行
            deferred.await()
            //join自己忽視錯誤,有沒有try catch都同樣
            //deferred.join()
        } catch (exception : NullPointerException) {
            println("catch exception")
        }
        println("end ${Thread.currentThread().id}")
        delay(3000)
    }
    
await打印出:
    start 1
    async Throwing exception 11
    catch exception
    end 1
     
可是對join打印出:
    start 1
    async Throwing exception 12
    end 1

複製代碼

CoroutineExceptionHandler捕獲

對於launch這種不傳播異常的協程,try catch沒法捕獲,能夠使用CoroutineExceptionHandler, CoroutineExceptionHandler沒法捕獲async內的異常

fun coroutineExceptionHandler ()  = runBlocking {

        val handleException = CoroutineExceptionHandler { _, throwable ->
            //能夠捕獲到launch中拋出的異常,可是不能捕獲async
            println("CoroutineExceptionHandler catch $throwable") 
        }
        println("start ${Thread.currentThread().id}")
        val job =  GlobalScope.launch(handleException) {
            println("launch Throwing exception ${Thread.currentThread().id}")
            throw NullPointerException()
        }
        
        //job.join()
        println("end ${Thread.currentThread().id}")
        delay(3000)
    }
    
打印出:
    launch/launch.join打印出:
    start 1
    launch Throwing exception 11
    CoroutineExceptionHandler catch java.lang.NullPointerException
    end 1
複製代碼

CoroutineExceptionHandler沒法捕獲async內的異常,不管有沒調用await()

fun asyncCoroutineExceptionHandler ()  = runBlocking {

        val handleException = CoroutineExceptionHandler { _, throwable ->
             //能夠捕獲到launch中拋出的異常,可是不能捕獲async
            println("CoroutineExceptionHandler catch $throwable") 
        }
        println("start ${Thread.currentThread().id}")
        val referred =  GlobalScope.async(handleException) {
            println("async Throwing exception ${Thread.currentThread().id}")
            throw IndexOutOfBoundsException()
        }
      //  referred.join()
     //   referred.await()
        println("end ${Thread.currentThread().id}")
        delay(3000)
    }
    
await打印出:
    start 1
    async Throwing exception 11
    java.lang.IndexOutOfBoundsException...
    
沒調用await打印:
    start 1
    async Throwing exception 11
    end 1
複製代碼

特殊的CancellationException

處於delay的協程再被cancel()後會拋出CancellationException異常,該異常不被捕獲也不會引發父協程的終止,也沒法被CoroutineExceptionHandler捕獲

fun launchCancelException()  = runBlocking {
        val handleException = CoroutineExceptionHandler { _, throwable ->
            //沒法捕獲到launch中拋出的CancellationException異常
            println("CoroutineExceptionHandler catch $throwable")
        }
        println("start ${Thread.currentThread().id}")
        launch(handleException) {
            println("launch Throwing exception ${Thread.currentThread().id}")
            //不會致使父協程終止
            throw CancellationException()
        }
        delay(3000)
        //由於子協程出現異常,默認會致使父協程終止,但CancellationException不會
        println("end ${Thread.currentThread().id}")
    }
    
打印出:
    start 1
    launch Throwing exception 1
    end 1
複製代碼

可是若是使用了await則異常會拋出在外部線程,須要進行捕獲,不然會致使啓動協程的線程崩潰

fun asyncCancelException()  = runBlocking {
        val handleException = CoroutineExceptionHandler { _, throwable ->
            //沒法捕獲到launch中拋出的CancellationException異常
            println("CoroutineExceptionHandler catch $throwable")
        }
        println("start ${Thread.currentThread().id}")
        val deferred = async(handleException) {
            println("async Throwing exception ${Thread.currentThread().id}")
            //不會致使父協程終止
            throw CancellationException()
        }
        //致使CancellationException異常拋出到該線程,下邊沒法執行
        deferred.await()
        //若沒有await則下邊能夠正常執行
        delay(3000)
        println("end ${Thread.currentThread().id}")
    }
    
打印出:
    start 1
    async Throwing exception 1
    end 1
    
使用了await,打印出:
    start 1
    async Throwing exception 1
    java.util.concurrent.CancellationException..
複製代碼

微信公衆號

相關文章
相關標籤/搜索