簡體   English   中英

GlobalScope 與 CoroutineScope 與生命周期范圍

[英]GlobalScope vs CoroutineScope vs lifecycleScope

我習慣於使用AsyncTask並且由於它的簡單性而很好地理解它。 但是Coroutines讓我感到困惑。 您能否以簡單的方式向我解釋以下各項的區別和目的是什么?

  1. GlobalScope.launch(Dispatchers.IO) {}
  2. GlobalScope.launch{}
  3. CoroutineScope(Dispatchers.IO).launch{}
  4. lifecycleScope.launch(Dispatchers.IO){}
  5. lifecycleScope.launch{}

首先,讓我們從定義開始說清楚。 如果您需要 Coroutines 和 Coroutines Flow 的教程或 Playground,您可以查看我創建的這個 教程/playground

Scope是 object 用於啟動僅包含一個 object 的協程,即CoroutineContext

public interface CoroutineScope {
    /**
     * The context of this scope.
     * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
     * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
     *
     * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
     */
    public val coroutineContext: CoroutineContext
}

協程上下文是一組定義協程如何執行的規則和配置。 在引擎蓋下,它是一種 map,具有一組可能的鍵和值。

協程上下文是不可變的,但是您可以使用加號運算符將元素添加到上下文中,就像您將元素添加到集合中一樣,生成新的上下文實例

定義協程行為的元素集是:

  • CoroutineDispatcher — 將工作分派到適當的線程。
  • Job——控制協程的生命周期。
  • CoroutineName — 協程的名稱,用於調試。
  • CoroutineExceptionHandler — 處理未捕獲的異常

Dispatchers Dispatchers 確定應該使用哪個線程池。 調度程序 class 也是CoroutineContext可以添加到 CoroutineContext

  • Dispatchers.Default :CPU 密集型工作,例如對大型列表進行排序、進行復雜計算等。 JVM 上的共享線程池支持它。

  • Dispatchers.IO :聯網或從文件讀寫。 簡而言之 - 任何輸入和 output,如名稱所述

  • Dispatchers.Main :用於在 Android 的主線程或 UI 線程中執行 UI 相關事件的強制調度程序。

例如,在 RecyclerView 中顯示列表、更新 Views 等。

您可以查看Android 的官方文檔以獲取有關調度程序的更多信息。

編輯即使官方文件指出

Dispatchers.IO - 此調度程序經過優化,可在主線程之外執行磁盤或網絡 I/O。 示例包括使用 Room 組件、讀取或寫入文件以及運行任何網絡操作。

Marko Topolnic的回答

IO 在一個特殊的、靈活的線程池上運行協程。 它僅作為一種解決方法存在,當您被迫使用傳統的阻塞 IO API 會阻塞其調用線程時。

也可能是對的。

Job協程本身由 Job 表示。 Job 是協程的句柄。 對於您創建的每個協程(通過啟動或異步),它會返回一個 Job 實例,該實例唯一地標識協程並管理其生命周期。 您還可以將 Job 傳遞給 CoroutineScope 以掌握其生命周期。

它負責協程的生命周期、取消和父子關系。 可以從當前協程的上下文中檢索當前作業:作業可以 go 通過一組狀態:新建、活動、完成、完成、取消和取消。 雖然我們無法訪問狀態本身,但我們可以訪問 Job 的屬性:isActive、isCancelled 和 isCompleted。

CoroutineScope定義了一個簡單的工廠 function ,它將CoroutineContext s 作為 arguments 來圍繞組合的 CoroutineContext 創建包裝器為

public fun CoroutineScope(context: CoroutineContext): CoroutineScope = ContextScope(if (context[Job]:= null) context else context + Job()) internal class ContextScope(context: CoroutineContext): CoroutineScope { override val coroutineContext: CoroutineContext = context // CoroutineScope is used intentionally for user-friendly representation override fun toString(): String = "CoroutineScope(coroutineContext=$coroutineContext)" }

如果提供的上下文還沒有,則創建一個Job元素。

我們來看一下 GlobalScope 源碼

/** * A global [CoroutineScope] not bound to any job. * * Global scope is used to launch top-level coroutines which are operating on the whole application lifetime * and are not cancelled prematurely. * Another use of the global scope is operators running in [Dispatchers.Unconfined], which don't have any job associated with them. * * Application code usually should use an application-defined [CoroutineScope]. Using * [async][CoroutineScope.async] or [launch][CoroutineScope.launch] * on the instance of [GlobalScope] is highly discouraged. * * Usage of this interface may look like this: * * ``` * fun ReceiveChannel<Int>.sqrt(): ReceiveChannel<Double> = GlobalScope.produce(Dispatchers.Unconfined) { * for (number in this) { * send(Math.sqrt(number)) * } * } * ``` */ public object GlobalScope: CoroutineScope { /** * Returns [EmptyCoroutineContext]. */ override val coroutineContext: CoroutineContext get() = EmptyCoroutineContext }

如您所見,它擴展CoroutineScope

1-只要您的應用程序還活着,GlobalScope 就活着,如果您在此 scope 中進行一些計數並旋轉您的設備,它將繼續執行任務/過程。

 GlobalScope.launch(Dispatchers.IO) {}

只要您的應用程序還活着,但在 IO 線程中運行,因為使用了Dispatchers.IO

2- 它與第一個相同,但默認情況下,如果您沒有任何上下文,則啟動使用 EmptyCoroutineContext,它使用 Dispatchers.Default,因此唯一的區別是線程與第一個。

3-這個與第一個相同,只有語法不同。

4- lifecycleScopeLifeCycleOwner的擴展,並綁定到 Activity 或 Fragment 的 lifCycle,其中 scope 在該 Activity 或 Fragment 被銷毀時被取消。

class Activity3CoroutineLifecycle : AppCompatActivity(), CoroutineScope {

    private lateinit var job: Job

    override val coroutineContext: CoroutineContext
        get() = job + Dispatchers.Main + CoroutineName("🙄 Activity Scope") + CoroutineExceptionHandler { coroutineContext, throwable ->
            println("🤬 Exception $throwable in context:$coroutineContext")
        }


    private val dataBinding by lazy {
        Activity3CoroutineLifecycleBinding.inflate(layoutInflater)
    }


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(dataBinding.root)
    
        job = Job()

        dataBinding. button.setOnClickListener {

            // This scope lives as long as Application is alive
            GlobalScope.launch {
                for (i in 0..300) {
                    println("🤪 Global Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")
                    delay(300)
                }
            }

            // This scope is canceled whenever this Activity's onDestroy method is called
            launch {
                for (i in 0..300) {
                    println("😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this")
                    withContext(Dispatchers.Main) {
                        dataBinding.tvResult.text = "😍 Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this"
                    }
                    delay(300)
                }
            }
        }

    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }

}

您也可以將其用作

class Activity3CoroutineLifecycle: AppCompatActivity(), CoroutineScope { private lateinit var job: Job override val coroutineContext: CoroutineContext get() = job + Dispatchers.Main + CoroutineName(" Activity Scope") + CoroutineExceptionHandler { coroutineContext, throwable -> println(" Exception $throwable in context:$coroutineContext") } private val dataBinding by lazy { Activity3CoroutineLifecycleBinding.inflate(layoutInflater) } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(dataBinding.root) job = Job() dataBinding. button.setOnClickListener { // This scope lives as long as Application is alive GlobalScope.launch { for (i in 0..300) { println(" Global Progress: $i in thread: ${Thread.currentThread().name}, scope: $this") delay(300) } } // This scope is canceled whenever this Activity's onDestroy method is called launch { for (i in 0..300) { println(" Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this") withContext(Dispatchers.Main) { dataBinding.tvResult.text = " Activity Scope Progress: $i in thread: ${Thread.currentThread().name}, scope: $this" } delay(300) } } } } override fun onDestroy() { super.onDestroy() job.cancel() } }

TL;博士

  1. GlobalScope.launch(Dispatchers.IO) :在Dispatchers.IO上啟動頂級協程。 協程未綁定並一直運行直到完成或取消。 由於程序員必須維護對join()cancel()的引用,因此通常不鼓勵。

  2. GlobalScope.launch :與上面相同,但GlobalScope使用Dispatchers.Default如果未指定。 經常氣餒。

  3. CoroutineScope(Dispatchers.IO).launch :創建一個使用Dispatchers.IO的協程 scope ,除非在協程構建器中指定了調度程序,即launch

  4. CoroutineScope(Dispatchers.IO).launch(Dispatchers.Main) :獎勵一。 使用與上述相同的協程 scope (如果 scope 實例相同!)但使用Dispatchers.Main覆蓋Dispatcher.IO

  5. lifecycleScope.launch(Dispatchers.IO) :在 AndroidX 提供的生命周期范圍內啟動協程。 一旦生命周期無效(即用戶離開片段),協程就會被取消。 使用Dispatchers.IO作為線程池。

  6. lifecycleScope.launch :與上面相同,但如果未指定,則使用Dispatchers.Main

解釋

協程 scope促進了結構化並發,因此您可以在同一個 scope 中啟動多個協程並取消 scope(這反過來又取消了該范圍內的所有協程)。 相反,GlobalScope 協程類似於線程,您需要在其中保留引用才能join()cancel()它。 這是Roman Elizarov 在 Medium 上的一篇優秀文章。

CoroutineDispatcher告訴協程構建器(在我們的例子中是launch {} )要使用哪個線程池。 有一些預定義的 Dispatcher 可用。

  • Dispatchers.Default - 使用與 CPU 核心數相等的線程池。 應該用於 CPU 密集型工作負載。
  • Dispatchers.IO - 使用 64 個線程池。 非常適合線程通常在等待的 IO 綁定工作負載; 可能用於網絡請求或磁盤讀/寫。
  • Dispatchers.Main (僅限 Android):使用主線程執行協程。 非常適合更新 UI 元素。

例子

我編寫了一個小演示片段,其中有 6 個函數對應於上述 6 個場景。 如果您在 Android 設備上運行以下片段; 打開片段,然后離開片段; 您會注意到只有 GlobalScope 協程仍然存在。 當生命周期無效時,生命周期協程會被生命周期范圍取消。 另一方面,CoroutineScope 會在我們明確完成的onPause()調用上被取消。

class DemoFragment : Fragment() {

    private val coroutineScope = CoroutineScope(Dispatchers.IO)

    init {
        printGlobalScopeWithIO()
        printGlobalScope()
        printCoroutineScope()
        printCoroutineScopeWithMain()
        printLifecycleScope()
        printLifecycleScopeWithIO()
    }

    override fun onPause() {
        super.onPause()
        coroutineScope.cancel()
    }

    private fun printGlobalScopeWithIO() = GlobalScope.launch(Dispatchers.IO) {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[GlobalScope-IO] I'm alive on thread ${Thread.currentThread().name}!")
        }
    }

    private fun printGlobalScope() = GlobalScope.launch {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[GlobalScope] I'm alive on ${Thread.currentThread().name}!")
        }
    }
    
    private fun printCoroutineScope() = coroutineScope.launch {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[CoroutineScope] I'm alive on ${Thread.currentThread().name}!")
        }
        Log.d("CoroutineDemo", "[CoroutineScope] I'm exiting!")
    }

    private fun printCoroutineScopeWithMain() = coroutineScope.launch(Dispatchers.Main) {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[CoroutineScope-Main] I'm alive on ${Thread.currentThread().name}!")
        }
        Log.d("CoroutineDemo", "[CoroutineScope-Main] I'm exiting!")
    }

    private fun printLifecycleScopeWithIO() = lifecycleScope.launch(Dispatchers.IO) {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[LifecycleScope-IO] I'm alive on ${Thread.currentThread().name}!")
        }
        Log.d("CoroutineDemo", "[LifecycleScope-IO]  I'm exiting!")
    }

    private fun printLifecycleScope() = lifecycleScope.launch {
        while (isActive) {
            delay(1000)
            Log.d("CoroutineDemo", "[LifecycleScope] I'm alive on ${Thread.currentThread().name}!")
        }
        Log.d("CoroutineDemo", "[LifecycleScope] I'm exiting!")
    }

}

我會沿着三個軸組織你的列表:

  1. GlobalScopeCoroutineScope()lifecycleScope范圍
  2. Dispatchers.IO與繼承(隱式)調度程序
  3. 在 scope vs. 中指定調度程序作為launch的參數

1. Scope的選擇

Kotlin 對協程的很大一部分是結構化並發,這意味着所有協程都被組織成一個層次結構,遵循它們的依賴關系。 如果您正在啟動一些后台工作,我們假設您希望它的結果出現在當前“工作單元”仍然處於活動狀態的某個時間點,即用戶沒有離開它並且不再關心它的結果。

在 Android 上,您擁有可以自動跟隨用戶在 UI 活動中導航的lifecycleScope范圍,因此您應該將其用作后台工作的父級,其結果將對用戶可見。

您可能還有一些即發即棄的工作,您只需要最終完成,但用戶不會等待其結果。 為此,您應該使用 Android 的WorkManager或類似的功能,即使用戶切換到另一個應用程序也可以安全地打開 go。 這些通常是將本地 state 與保存在服務器端的 state 同步的任務。

在這張圖中, GlobalScope基本上是結構化並發的一個逃生艙。 它允許您滿足提供 scope 的形式,但破壞了它應該實現的所有機制。 GlobalScope永遠不能被取消,並且它沒有父級。

編寫CoroutineScope(...).launch是錯誤的,因為您創建的 scope object 沒有您立即忘記的父級,因此無法取消它。 它與使用GlobalScope類似,但更 hacky。

2.調度員的選擇

協程調度器決定您的協程可以在哪些線程上運行。 在 Android 上,您應該關心三個調度程序:

  1. Main在單個 GUI 線程上運行所有內容。 它應該是你的主要選擇。
  2. IO在一個特殊的、靈活的線程池上運行協程。 它僅作為一種解決方法存在,當您被迫使用傳統的阻塞 IO API 會阻塞其調用線程時。
  3. Default也使用線程池,但大小固定,等於 CPU 核心數。 將它用於計算密集型工作,這些工作需要足夠長的時間導致 GUI 出現故障(例如,圖像壓縮/解壓縮)。

3. 在哪里指定 Dispatcher

首先,您應該了解您正在使用的協程 scope 中指定的調度程序。 GlobalScope沒有指定任何內容,因此一般默認值是有效的,即Default調度程序。 lifecycleScope指定Main調度程序。

我們已經解釋過您不應該使用CoroutineScope構造函數創建臨時作用域,因此指定顯式調度程序的正確位置是作為launch的參數。

在技術細節上,當您編寫someScope.launch(someDispatcher)時, someDispatcher參數實際上是一個成熟的協程上下文 object,它恰好有一個元素,即調度程序。 您正在啟動的協程通過將協程 scope 中的一個和您作為參數提供的一個組合來為自己創建一個新的上下文。 最重要的是,它為自己創建了一個新的Job並將其添加到上下文中。 該作業是在上下文中繼承的作業的子作業。

你應該知道,如果你想啟動suspend function 你需要在CoroutineScope中進行。 每個CoroutineScope都有CoroutineContext 其中CoroutineContext是一個 map,它可以包含Dispatcher (將工作分派到適當的線程)、 Job (控制協程的生命周期)、 CoroutineExceptionHandler (處理未捕獲的異常)、 CoroutineName (協程的名稱,用於調試)。

  1. GlobalScope.launch(Dispatchers.IO) {} - GlobalScope.launch創建全局協程並用於不應取消的操作,但更好的選擇是在應用程序 class 中創建自定義 scope,並將其注入需要的 ZA2F2ED4F8EBC2CBBDC214它。 這樣做的好處是讓您能夠使用CoroutineExceptionHandler或替換CoroutineDispatcher進行測試。
  2. GlobalScope.launch{} - 與GlobalScope.launch(Dispatchers.IO) {}相同,但在Dispatchers.Default上運行coroutines Dispatchers.Default是一個默認Dispatcher ,如果在其上下文中未指定任何調度程序,則使用該調度程序。
  3. CoroutineScope(Dispatchers.IO).launch{} - 它使用一個參數創建 scope 並在IO線程上啟動新的coroutine 將與啟動它的 object 一起銷毀。 但是如果你想正確地結束你的工作,你應該為CoroutineScope手動調用.cancel()
  4. lifecycleScope.launch(Dispatchers.IO){} - 它是可從LifecycleLifecycleOwner ( ActivityFragment ) 獲得的現有范圍,並以依賴androidx.lifecycle:lifecycle-runtime-ktx:*出現在您的項目中。 使用它,您可以擺脫手動創建CoroutineScope 它將在Dispatchers.IO中運行您的作業而不阻塞MainThread ,並確保您的作業將在您的lifecycle被銷毀時被取消。
  5. lifecycleScope.launch{} - 與lifecycleScope.launch(Dispatchers.IO){}相同,它使用默認的Dispatchers.Main參數為您創建CoroutinesScope並在Dispatcher.Main中運行您的coroutines ,這意味着您可以使用UI

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM