Kotlin 協(xié)程生命周期和內(nèi)存管理

1. 為什么要關(guān)閉或者取消一個(gè)協(xié)程

協(xié)程是一種輕量級(jí)的線程,可以在一個(gè)線程中并發(fā)執(zhí)行多個(gè)任務(wù)。但是,并不是所有的協(xié)程都需要一直運(yùn)行,有些協(xié)程可能會(huì)在某些條件下失去執(zhí)行的必要或者意義。例如,協(xié)程可能會(huì)因?yàn)槌瑫r(shí)、錯(cuò)誤、用戶請(qǐng)求、業(yè)務(wù)邏輯等原因需要終止。如果不及時(shí)關(guān)閉或者取消這些協(xié)程,可能會(huì)導(dǎo)致內(nèi)存泄漏、資源浪費(fèi)、程序崩潰等問題。因此,我們需要在合適的時(shí)機(jī)關(guān)閉或者取消不需要的協(xié)程,以保證程序的健壯性和性能。

1.1 協(xié)程的生命周期

協(xié)程的生命周期是指協(xié)程從創(chuàng)建到銷毀的過程,它可以分為以下幾個(gè)階段:

  • 新建:協(xié)程被創(chuàng)建,但還沒有開始執(zhí)行
  • 活動(dòng):協(xié)程開始執(zhí)行,并在遇到 suspend 函數(shù)時(shí)暫停和恢復(fù)
  • 完成:協(xié)程正常結(jié)束或者異常終止,并返回結(jié)果或者拋出異常
  • 取消:協(xié)程收到取消信號(hào),并停止執(zhí)行

下圖是一個(gè)協(xié)程生命周期的示意圖:

協(xié)程生命周期的示意圖

1.2 協(xié)程的狀態(tài)

協(xié)程的狀態(tài)是指協(xié)程在生命周期中的具體狀態(tài),它可以用一個(gè) Job 對(duì)象來表示。Job 對(duì)象是一個(gè)接口,它定義了一些屬性和方法來控制和監(jiān)視協(xié)程的狀態(tài)。Job 對(duì)象可以分為以下幾種類型:

  • Job:最基本的 Job 類型,它只有一個(gè) isActive 屬性,表示協(xié)程是否處于活動(dòng)狀態(tài)
  • CancellableJob:一個(gè)可取消的 Job 類型,它有一個(gè) isCancelled 屬性,表示協(xié)程是否已經(jīng)被取消,以及一個(gè) cancel 方法,用于取消協(xié)程
  • CompletableJob:一個(gè)可完成的 Job 類型,它有一個(gè) isCompleted 屬性,表示協(xié)程是否已經(jīng)完成,以及一個(gè) complete 方法,用于完成協(xié)程
  • Deferred:一個(gè)可延遲的 Job 類型,它有一個(gè) await 方法,用于等待協(xié)程返回結(jié)果

下圖是一個(gè) Job 類型層次結(jié)構(gòu)的示意圖:

Job 類型層次結(jié)構(gòu)的示意圖

2. 如何關(guān)閉或者取消一個(gè)協(xié)程

關(guān)閉或者取消一個(gè)協(xié)程主要有兩個(gè)步驟:發(fā)送取消信號(hào)和捕捉取消信號(hào)。發(fā)送取消信號(hào)是指向協(xié)程發(fā)送一個(gè)終止執(zhí)行的通知,讓協(xié)程知道需要停止運(yùn)行。捕捉取消信號(hào)是指協(xié)程在接收到取消信號(hào)后,執(zhí)行相應(yīng)的操作,如釋放資源、清理狀態(tài)、返回結(jié)果等。發(fā)送和捕捉取消信號(hào)的方式有多種,常見的有以下幾種:

  • 使用全局變量:如果全局變量為真就退出
  • 使用通道:協(xié)程在通道里面取到 true 或者 nil 就退出
  • 使用 context:通過調(diào)用 ctx.Done () 方法通知所有的協(xié)程退出

2.1 使用全局變量

使用全局變量是一種最簡(jiǎn)單的取消協(xié)程的方法,它只需要定義一個(gè)全局的布爾變量,然后在協(xié)程中檢查這個(gè)變量的值,如果為真就退出。這種方法的優(yōu)點(diǎn)是簡(jiǎn)單易用,缺點(diǎn)是不夠靈活,不能針對(duì)單個(gè)協(xié)程進(jìn)行取消,也不能傳遞取消的原因。下面是一個(gè)使用全局變量取消協(xié)程的示例:

import kotlinx.coroutines.*

// 定義一個(gè)全局的布爾變量
var isCancelled = false

fun main() {
    // 創(chuàng)建一個(gè) CoroutineScope 對(duì)象
    val scope = CoroutineScope(Dispatchers.Default)
    // 在 scope 中啟動(dòng)一個(gè)協(xié)程
    scope.launch {
        // 在協(xié)程中循環(huán)打印信息
        while (true) {
            // 檢查全局變量的值,如果為真就退出
            if (isCancelled) {
                println("Coroutine is cancelled")
                break
            }
            println("Coroutine is running")
            // 暫停一段時(shí)間
            delay(1000L)
        }
    }
    // 主線程等待一段時(shí)間
    Thread.sleep(3000L)
    // 修改全局變量的值為真
    isCancelled = true
    // 主線程等待一段時(shí)間
    Thread.sleep(1000L)
}

輸出結(jié)果:

Coroutine is running
Coroutine is running
Coroutine is running
Coroutine is cancelled

2.2 使用通道

使用通道是一種更靈活的取消協(xié)程的方法,它可以利用 Kotlin 的 Channel 類來實(shí)現(xiàn)協(xié)程之間的通信。Channel 類是一種類似于隊(duì)列的數(shù)據(jù)結(jié)構(gòu),它可以讓協(xié)程之間發(fā)送和接收數(shù)據(jù)。我們可以在 Channel 中發(fā)送一個(gè)特殊的值,如 true 或者 nil,來表示取消信號(hào),然后在協(xié)程中從 Channel 中取出這個(gè)值,如果收到了就退出。這種方法的優(yōu)點(diǎn)是可以針對(duì)單個(gè)或者多個(gè)協(xié)程進(jìn)行取消,也可以傳遞取消的原因,缺點(diǎn)是需要額外創(chuàng)建和管理 Channel 對(duì)象。下面是一個(gè)使用通道取消協(xié)程的示例:

import kotlinx.coroutines.*

fun main() {
    // 創(chuàng)建一個(gè) CoroutineScope 對(duì)象
    val scope = CoroutineScope(Dispatchers.Default)
    // 創(chuàng)建一個(gè) Channel 對(duì)象
    val channel = Channel<Boolean>()
    // 在 scope 中啟動(dòng)一個(gè)協(xié)程
    scope.launch {
        // 在協(xié)程中循環(huán)打印信息
        while (true) {
            // 從 Channel 中取出一個(gè)值,如果為 true 或者 nil 就退出
            val value = channel.poll()
            if (value == true || value == null) {
                println("Coroutine is cancelled")
                break
            }
            println("Coroutine is running")
            // 暫停一段時(shí)間
            delay(1000L)
        }
    }
    // 主線程等待一段時(shí)間
    Thread.sleep(3000L)
    // 向 Channel 中發(fā)送一個(gè) true 值
    channel.send(true)
    // 主線程等待一段時(shí)間
    Thread.sleep(1000L)
}

輸出結(jié)果:

Coroutine is running
Coroutine is running
Coroutine is running
Coroutine is cancelled

2.3 使用 context

使用 context 是一種最推薦的取消協(xié)程的方法,它可以利用 Kotlin 的 CoroutineContext 類來實(shí)現(xiàn)協(xié)程之間的上下文傳遞。CoroutineContext 類是一種類似于字典的數(shù)據(jù)結(jié)構(gòu),它可以存儲(chǔ)一些與協(xié)程相關(guān)的元素,如 Job、Dispatcher、Name 等。我們可以在 CoroutineContext 中獲取一個(gè) Job 對(duì)象,并調(diào)用它的 cancel 方法來發(fā)送取消信號(hào),然后在協(xié)程中調(diào)用 isActive 屬性或者 yield函數(shù)來捕捉取消信號(hào),并執(zhí)行相應(yīng)的操作。這種方法的優(yōu)點(diǎn)是可以利用協(xié)程的層次結(jié)構(gòu)和作用域來實(shí)現(xiàn)協(xié)程的自動(dòng)取消,也可以使用 cancelAndJoin 或者 await 等函數(shù)來等待協(xié)程的關(guān)閉,缺點(diǎn)是需要注意協(xié)程的取消異常和取消點(diǎn)的設(shè)置。下面是一個(gè)使用 context 取消協(xié)程的示例:

import kotlinx.coroutines.*

fun main() {
    // 創(chuàng)建一個(gè) CoroutineScope 對(duì)象
    val scope = CoroutineScope(Dispatchers.Default)
    // 在 scope 中啟動(dòng)一個(gè)協(xié)程
    val job = scope.launch {
        // 在協(xié)程中啟動(dòng)另一個(gè)子協(xié)程
        launch {
            // 在子協(xié)程中循環(huán)打印信息
            while (true) {
                // 檢查 isActive 屬性,如果為假就退出
                if (!isActive) {
                    println("Child coroutine is cancelled")
                    break
                }
                println("Child coroutine is running")
                // 暫停一段時(shí)間
                delay(1000L)
            }
        }
        // 在協(xié)程中循環(huán)打印信息
        while (true) {
            // 調(diào)用 yield 函數(shù),如果收到取消信號(hào)就退出
            yield()
            println("Parent coroutine is running")
            // 暫停一段時(shí)間
            delay(1000L)
        }
    }
    // 主線程等待一段時(shí)間
    Thread.sleep(3000L)
    // 調(diào)用 job 的 cancel 方法,向所有的子協(xié)程發(fā)送取消信號(hào)
    job.cancel()
    // 調(diào)用 job 的 join 方法,等待所有的子協(xié)程關(guān)閉
    job.join()
}

輸出結(jié)果:

Child coroutine is running
Parent coroutine is running
Child coroutine is running
Parent coroutine is running
Child coroutine is running
Parent coroutine is running
Child coroutine is cancelled
Parent coroutine is cancelled

3. 如何管理協(xié)程的內(nèi)存

協(xié)程的內(nèi)存管理主要涉及到堆棧幀和協(xié)程對(duì)象的創(chuàng)建和銷毀。堆棧幀是用于保存協(xié)程局部變量和狀態(tài)的數(shù)據(jù)結(jié)構(gòu),每個(gè)協(xié)程都有自己的堆棧幀。協(xié)程對(duì)象是用于保存協(xié)程元數(shù)據(jù)和引用的對(duì)象,每個(gè)協(xié)程都對(duì)應(yīng)一個(gè)協(xié)程對(duì)象。協(xié)程的內(nèi)存管理需要考慮以下幾個(gè)方面:

  • 堆棧幀的大小和數(shù)量:堆棧幀越大或越多,占用的內(nèi)存越多
  • 堆棧幀的復(fù)制和恢復(fù):堆棧幀在暫停和恢復(fù)時(shí)需要進(jìn)行復(fù)制和恢復(fù),這會(huì)消耗一定的時(shí)間和空間
  • 協(xié)程對(duì)象的創(chuàng)建和銷毀:協(xié)程對(duì)象在創(chuàng)建和銷毀時(shí)需要分配和回收內(nèi)存,這會(huì)產(chǎn)生一定的開銷
  • 協(xié)程對(duì)象的引用:協(xié)程對(duì)象如果被其他對(duì)象引用,可能會(huì)導(dǎo)致內(nèi)存泄漏或無法回收

3.1 堆棧幀的大小和數(shù)量

堆棧幀是用于保存協(xié)程局部變量和狀態(tài)的數(shù)據(jù)結(jié)構(gòu),每個(gè)協(xié)程都有自己的堆棧幀。堆棧幀的大小取決于協(xié)程中定義的局部變量的數(shù)量和類型,以及調(diào)用的函數(shù)的數(shù)量和參數(shù)。堆棧幀的數(shù)量取決于協(xié)程中調(diào)用的函數(shù)的層次深度。堆棧幀越大或越多,占用的內(nèi)存越多。

為了減少堆棧幀的大小和數(shù)量,我們可以采取以下一些措施:

  • 盡量避免在協(xié)程中定義過多或過大的局部變量,尤其是數(shù)組、集合、映射等容器類型,它們會(huì)消耗大量的內(nèi)存空間。
  • 盡量避免在協(xié)程中調(diào)用過多或過深的函數(shù),尤其是遞歸函數(shù),它們會(huì)增加堆棧幀的數(shù)量和深度。
  • 盡量避免在協(xié)程中使用全局變量或者閉包,它們會(huì)導(dǎo)致協(xié)程持有外部對(duì)象的引用,從而增加內(nèi)存的占用和泄漏的風(fēng)險(xiǎn)。

3.2 堆棧幀的復(fù)制和恢復(fù)

堆棧幀在暫停和恢復(fù)時(shí)需要進(jìn)行復(fù)制和恢復(fù),這會(huì)消耗一定的時(shí)間和空間。當(dāng)一個(gè)協(xié)程遇到一個(gè) suspend 函數(shù)時(shí),它會(huì)將自己的堆棧幀復(fù)制到一個(gè) Continuation 對(duì)象中,并將控制權(quán)交給其他協(xié)程或者調(diào)用者。當(dāng)一個(gè)協(xié)程收到一個(gè) resume 信號(hào)時(shí),它會(huì)從 Continuation 對(duì)象中恢復(fù)自己的堆棧幀,并從暫停的地方繼續(xù)執(zhí)行。

為了減少堆棧幀的復(fù)制和恢復(fù),我們可以采取以下一些措施:

  • 盡量避免在協(xié)程中頻繁地調(diào)用 suspend 函數(shù),尤其是在循環(huán)或者條件判斷中,它們會(huì)導(dǎo)致協(xié)程不斷地暫停和恢復(fù),從而增加堆棧幀的復(fù)制和恢復(fù)的次數(shù)。
  • 盡量避免在協(xié)程中使用多個(gè) suspend 函數(shù),尤其是嵌套或者并發(fā)地使用,它們會(huì)導(dǎo)致協(xié)程創(chuàng)建多個(gè) Continuation 對(duì)象,從而增加堆棧幀的復(fù)制和恢復(fù)的開銷。
  • 盡量避免在協(xié)程中使用非局部返回或者異常拋出,它們會(huì)導(dǎo)致協(xié)程跳出當(dāng)前的執(zhí)行點(diǎn),從而增加堆棧幀的復(fù)制和恢復(fù)的難度。

3.3 協(xié)程對(duì)象的創(chuàng)建和銷毀

協(xié)程對(duì)象是用于保存協(xié)程元數(shù)據(jù)和引用的對(duì)象,每個(gè)協(xié)程都對(duì)應(yīng)一個(gè)協(xié)程對(duì)象。協(xié)程對(duì)象在創(chuàng)建和銷毀時(shí)需要分配和回收內(nèi)存,這會(huì)產(chǎn)生一定的開銷。協(xié)程對(duì)象的類型取決于協(xié)程構(gòu)建器的選擇,如 launch、async、coroutineScope 等。不同類型的協(xié)程對(duì)象有不同的功能和性能特點(diǎn)。

為了減少協(xié)程對(duì)象的創(chuàng)建和銷毀,我們可以采取以下一些措施:

  • 根據(jù)業(yè)務(wù)需求合理選擇協(xié)程構(gòu)建器,盡量避免創(chuàng)建不必要或過多的協(xié)程對(duì)象。例如,如果不需要返回結(jié)果或等待子協(xié)程完成,就可以使用 launch 而不是 async;如果不需要?jiǎng)?chuàng)建新的作用域或上下文,就可以使用 runBlocking 而不是 coroutineScope。
  • 根據(jù)業(yè)務(wù)邏輯合理劃分協(xié)程作用域,盡量避免創(chuàng)建超出生命周期范圍的協(xié)程對(duì)象。例如,如果需要在 ViewModel 中使用協(xié)程,就可以使用 viewModelScope 而不是 GlobalScope;如果需要在 Activity 中使用協(xié)程,就可以使用 lifecycleScope 而不是 CoroutineScope。
  • 根據(jù)業(yè)務(wù)場(chǎng)景合理重用或回收協(xié)程對(duì)象,盡量避免頻繁地創(chuàng)建或銷毀協(xié)程對(duì)象。例如,如果需要在多個(gè)地方使用相同的上下文或調(diào)度器,就可以使用 withContext 而不是每次都創(chuàng)建新的 CoroutineScope;如果需要在多次調(diào)用之間保持協(xié)程狀態(tài)或結(jié)果,就可以使用 CoroutineStart.LAZY 或者 flow 而不是每次都重新啟動(dòng)新的協(xié)程。

3.4 協(xié)程對(duì)象的引用

協(xié)程對(duì)象如果被其他對(duì)象引用,可能會(huì)導(dǎo)致內(nèi)存泄漏或無法回收。協(xié)程對(duì)象如果被其他對(duì)象引用,可能會(huì)導(dǎo)致內(nèi)存泄漏或無法回收。內(nèi)存泄漏是指協(xié)程對(duì)象占用的內(nèi)存無法被垃圾回收器回收,從而導(dǎo)致內(nèi)存資源的浪費(fèi)。無法回收是指協(xié)程對(duì)象無法正常地完成或取消,從而導(dǎo)致協(xié)程狀態(tài)的不一致。

為了避免協(xié)程對(duì)象的引用問題,我們可以采取以下一些措施:

  • 盡量避免在協(xié)程中使用全局變量或者閉包,它們會(huì)導(dǎo)致協(xié)程持有外部對(duì)象的引用,從而增加內(nèi)存的占用和泄漏的風(fēng)險(xiǎn)。
  • 盡量避免在協(xié)程中使用強(qiáng)引用或者長(zhǎng)生命周期的引用,它們會(huì)導(dǎo)致協(xié)程對(duì)象無法被垃圾回收器回收,從而導(dǎo)致內(nèi)存資源的浪費(fèi)。
  • 盡量使用弱引用或者短生命周期的引用,它們會(huì)在協(xié)程對(duì)象不再被使用時(shí)自動(dòng)釋放,從而避免內(nèi)存泄漏或無法回收的問題。
  • 盡量使用協(xié)程作用域或者生命周期感知組件,它們會(huì)在合適的時(shí)機(jī)自動(dòng)取消或完成協(xié)程,從而避免協(xié)程狀態(tài)的不一致。

4. 協(xié)程取消和線程取消的思路對(duì)比

協(xié)程取消和線程取消都是一種終止執(zhí)行的操作,但是它們有一些不同的思路和特點(diǎn)。下面我們來對(duì)比一下協(xié)程取消和線程取消的思路:

協(xié)程取消 線程取消
使用 CancellationException 異常 使用 InterruptedException 異常
可以傳遞取消的原因 不能傳遞任何信息
需要協(xié)程主動(dòng)響應(yīng)并執(zhí)行操作 需要線程被強(qiáng)制終止并處理異常
可以利用協(xié)程作用域或生命周期感知組件自動(dòng)管理 需要手動(dòng)管理或使用 ExecutorService 自動(dòng)管理

4.1 協(xié)程取消的思路

協(xié)程取消的思路是基于協(xié)作機(jī)制的,也就是說協(xié)程需要主動(dòng)響應(yīng)取消信號(hào),并執(zhí)行相應(yīng)的操作。協(xié)程取消的特點(diǎn)有以下幾個(gè):

  • 協(xié)程取消是通過拋出一個(gè)特殊的異常 CancellationException 來實(shí)現(xiàn)的,這個(gè)異常可以攜帶取消的原因,并且不會(huì)被捕獲或者打印
  • 協(xié)程取消是通過調(diào)用 Job 的 cancel 方法來發(fā)送取消信號(hào)的,這個(gè)方法可以傳入一個(gè) CancellationException 實(shí)例來提供更多關(guān)于本次取消的詳細(xì)信息
  • 協(xié)程取消是通過檢查 Job 的 isActive 屬性或者調(diào)用 yield 函數(shù)來捕捉取消信號(hào)的,這些操作會(huì)在協(xié)程被取消時(shí)拋出 CancellationException 異常
  • 協(xié)程取消是通過使用協(xié)程作用域或者生命周期感知組件來自動(dòng)管理的,它們會(huì)在合適的時(shí)機(jī)自動(dòng)取消或完成協(xié)程

下面是一個(gè)協(xié)程取消的實(shí)例代碼,它使用了 viewModelScope 來自動(dòng)管理協(xié)程的生命周期,并在協(xié)程中調(diào)用了一個(gè) suspend 函數(shù)來模擬耗時(shí)操作。當(dāng)用戶點(diǎn)擊取消按鈕時(shí),協(xié)程會(huì)收到取消信號(hào),并執(zhí)行相應(yīng)的操作。

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.*

class MyViewModel : ViewModel() {

    // 定義一個(gè)可延遲的 Job 對(duì)象
    private var job: Deferred<String>? = null

    // 定義一個(gè) suspend 函數(shù),用于模擬耗時(shí)操作
    private suspend fun doSomething(): String {
        // 暫停 5 秒鐘
        delay(5000L)
        // 返回結(jié)果
        return "Done"
    }

    // 定義一個(gè)函數(shù),用于啟動(dòng)協(xié)程
    fun startCoroutine() {
        // 在 viewModelScope 中啟動(dòng)一個(gè)協(xié)程,并將返回的 Job 對(duì)象賦值給 job
        job = viewModelScope.async {
            try {
                // 在協(xié)程中調(diào)用 suspend 函數(shù),并捕獲可能拋出的 CancellationException 異常
                val result = doSomething()
                // 在 UI 線程中更新視圖
                withContext(Dispatchers.Main) {
                    textView.text = result
                }
            } catch (e: CancellationException) {
                // 在 UI 線程中顯示取消的原因
                withContext(Dispatchers.Main) {
                    textView.text = e.message
                }
            }
        }
    }

    // 定義一個(gè)函數(shù),用于取消協(xié)程
    fun cancelCoroutine() {
        // 調(diào)用 job 的 cancel 方法,并傳入一個(gè) CancellationException 實(shí)例,攜帶取消的原因
        job?.cancel(CancellationException("User cancelled"))
    }
}

4.2 線程取消的思路

線程取消的思路是基于中斷機(jī)制的,也就是說線程需要被外部強(qiáng)制終止,并處理中斷異常。線程取消的特點(diǎn)有以下幾個(gè):

  • 線程取消是通過拋出一個(gè)普通的異常 InterruptedException 來實(shí)現(xiàn)的,這個(gè)異常需要被捕獲或者打印,并且不能攜帶取消的原因
  • 線程取消是通過調(diào)用 Thread 的 interrupt 方法來發(fā)送中斷信號(hào)的,這個(gè)方法不可以傳入任何參數(shù)來提供更多關(guān)于本次中斷的詳細(xì)信息
  • 線程取消是通過檢查 Thread 的 isInterrupted 方法或者調(diào)用可中斷的方法(如 sleep、wait、join 等)來捕捉中斷信號(hào)的,這些操作會(huì)在線程被中斷時(shí)拋出 InterruptedException 異常
  • 線程取消是通過手動(dòng)管理或者使用 ExecutorService 來自動(dòng)管理的,它們需要在合適的時(shí)機(jī)手動(dòng)調(diào)用 interrupt 方法或者 shutdown 方法來終止線程

下面是一個(gè)線程取消的實(shí)例代碼,它使用了 ExecutorService 來自動(dòng)管理線程的生命周期,并在線程中調(diào)用了一個(gè)可中斷的方法來模擬耗時(shí)操作。當(dāng)用戶點(diǎn)擊取消按鈕時(shí),線程會(huì)收到中斷信號(hào),并處理中斷異常。

import java.util.concurrent.*;

public class MyActivity extends AppCompatActivity {

    // 定義一個(gè) ExecutorService 對(duì)象,用于創(chuàng)建和管理線程池
    private ExecutorService executor;

    // 定義一個(gè) Future 對(duì)象,用于接收線程的返回結(jié)果或異常
    private Future<String> future;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 初始化 ExecutorService 對(duì)象,創(chuàng)建一個(gè)單線程的線程池
        executor = Executors.newSingleThreadExecutor();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 調(diào)用 ExecutorService 的 shutdown 方法,關(guān)閉線程池并終止所有的線程
        executor.shutdown();
    }

    // 定義一個(gè)函數(shù),用于啟動(dòng)線程
    public void startThread() {
        // 在 ExecutorService 中提交一個(gè) Callable 任務(wù),并將返回的 Future 對(duì)象賦值給 future
        future = executor.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                try {
                    // 在線程中調(diào)用可中斷的方法,并捕獲可能拋出的 InterruptedException 異常
                    Thread.sleep(5000L);
                    // 返回結(jié)果
                    return "Done";
                } catch (InterruptedException e) {
                    // 拋出異常,讓 Future 對(duì)象接收
                    throw e;
                }
            }
        });
        // 在主線程中創(chuàng)建一個(gè) Handler 對(duì)象,用于更新 UI
        Handler handler = new Handler(Looper.getMainLooper());
        // 在主線程中創(chuàng)建一個(gè) Runnable 對(duì)象,用于獲取 Future 對(duì)象的結(jié)果或異常
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                try {
                    // 從 Future 對(duì)象中獲取結(jié)果或異常,并在 UI 線程中更新視圖
                    String result = future.get();
                    textView.setText(result);
                } catch (ExecutionException e) {
                    // 如果 Future 對(duì)象拋出了異常,獲取其中的原因,并在 UI 線程中顯示
                    Throwable cause = e.getCause();
                    textView.setText(cause.getMessage());
                } catch (InterruptedException e) {
                    // 如果主線程被中斷,忽略異常
                }
            }
        };
        // 在 Handler 對(duì)象中延遲執(zhí)行 Runnable 對(duì)象,等待 Future 對(duì)象完成
        handler.postDelayed(runnable, 5000L);
    }

    // 定義一個(gè)函數(shù),用于取消線程
    public void cancelThread() {
        // 調(diào)用 Future 對(duì)象的 cancel 方法,并傳入一個(gè) true 值,表示發(fā)送中斷信號(hào)
        future.cancel(true);
    }
}

4.3 思路對(duì)比

從上面的對(duì)比可以看出,協(xié)程取消和線程取消有以下幾點(diǎn)不同:

  • 協(xié)程取消使用了一種特殊的異常 CancellationException ,而線程取消使用了一種普通的異常 InterruptedException 。
  • 協(xié)程取消可以傳遞更多關(guān)于本次取消的詳細(xì)信息,而線程取消不能傳遞任何信息。
  • 協(xié)程取消需要協(xié)程主動(dòng)響應(yīng)并執(zhí)行相應(yīng)的操作,而線程取消需要線程被強(qiáng)制終止并處理異常。
  • 協(xié)程取消可以利用協(xié)程作用域或者生命周期感知組件來自動(dòng)管理,而線程取消需要手動(dòng)管理或者使用 ExecutorService 來自動(dòng)管理。

總體來說,協(xié)程取消比線程取消更靈活、更優(yōu)雅、更高效。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容