精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

協程的概念及協程的掛起函數介紹

jf_78858299 ? 來源:labuladong ? 作者:labuladong ? 2023-04-19 10:20 ? 次閱讀

什么是協程

協程是一種輕量級的線程,它可以在單個線程中實現并發執行。與線程不同,協程不需要操作系統的上下文切換,因此可以更高效地使用系統資源。Kotlin 協程是 Kotlin 語言的一項特性,它提供了一種簡單而強大的方式來處理異步任務。

相關的基本概念

掛起函數

掛起函數是一種特殊的函數,它可以在執行過程中暫停并等待某些操作完成。在 Kotlin 中,掛起函數使用 suspend 關鍵字進行標記。掛起函數的特點是可以在函數內部使用 suspend 關鍵字標記的其他掛起函數,這些掛起函數會在執行過程中暫停當前協程的執行,并等待異步任務的結果。當異步任務完成后,協程會自動恢復執行,并將結果返回給調用方。

以下是一個使用掛起函數的例子,該例子使用 Retrofit 庫進行網絡請求:

suspend fun fetchUser(userId: String): User {
    return withContext(Dispatchers.IO) {
        // 創建 Retrofit 實例
        val retrofit = Retrofit.Builder()
            .baseUrl("https://api.github.com/")
            .addConverterFactory(GsonConverterFactory.create())
            .build()
        // 創建 API 接口
        val apiService = retrofit.create(ApiService::class.java)
        // 發起網絡請求
        val response = apiService.getUser(userId)
        // 解析響應
        val user = response.body()
        // 返回結果
        user ?: throw IllegalStateException("User not found")
    }
}

在上面的例子中,fetchUser 函數使用了 withContext 函數來切換到 IO 線程執行網絡請求。在網絡請求的過程中,使用了 Retrofit 庫提供的掛起函數 getUser 來發起網絡請求,并等待響應結果。當響應結果返回后,協程會自動恢復執行,并將結果返回給調用方。

需要注意的是,掛起函數只能在協程中使用,不能在普通的函數中使用。在使用掛起函數時,我們需要將其包裝在協程作用域中,以便管理協程的生命周期。例如:

val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
    val user = fetchUser("123")
    // 處理用戶數據
}
scope.cancel()

在上面的例子中,我們使用了協程作用域來管理協程的生命周期。在協程作用域中,我們使用 launch 函數來啟動一個新的協程,并在其中調用 fetchUser 函數來獲取用戶數據。當協程作用域結束時,協程會自動取消,避免了線程泄漏的問題。

協程作用域

協程作用域是一種管理協程的機制,它可以確保協程在指定的作用域內運行,并在作用域結束時自動取消協程。在 Kotlin 中,協程作用域由 CoroutineScope 接口表示。

協程作用域的主要作用是管理協程的生命周期。在協程作用域內啟動的協程會自動繼承作用域的上下文和調度器,并在作用域結束時自動取消。這樣,我們就可以避免協程泄漏和線程泄漏的問題,提高程序的性能和穩定性。

協程作用域還可以將多個協程組合在一起,實現并發執行。在協程作用域中,我們可以使用 async 函數來啟動一個新的協程,并返回一個 Deferred 對象,該對象可以用于獲取協程的執行結果。例如:

val scope = CoroutineScope(Dispatchers.IO)
val deferred1 = scope.async { fetchUser("123") }
val deferred2 = scope.async { fetchUser("456") }
val users = listOf(deferred1.await(), deferred2.await())
scope.cancel()

在上面的例子中,我們使用協程作用域來管理兩個協程的生命周期,并使用 async 函數來啟動兩個協程,分別獲取用戶數據。在獲取用戶數據的過程中,我們使用了 await 函數來等待協程的執行結果。當兩個協程都執行完成后,我們將結果保存到 users 列表中。

?需要注意的是,協程作用域是一種輕量級的機制,它不會創建新的線程或進程。協程作用域中的協程會在當前線程中執行,并使用協程調度器來管理協程的執行。因此,我們需要根據具體的需求選擇合適的協程調度器,以便實現最佳的性能和響應速度。

?

Dispatchers.IO 是 Kotlin 協程庫中的一個協程調度器,它用于將協程分配到 IO 線程池中執行。在協程中執行 IO 操作時,我們通常會使用 Dispatchers.IO 調度器來避免阻塞主線程或其他重要線程。

Android 應用程序中,主線程通常用于處理 UI 事件和更新 UI 界面,因此我們應該盡量避免在主線程中執行耗時的 IO 操作。如果我們在主線程中執行耗時的 IO 操作,會導致 UI 界面卡頓或無響應,影響用戶體驗。為了避免在主線程中執行耗時的 IO 操作,我們可以使用 Dispatchers.IO 調度器將協程分配到 IO 線程池中執行。IO 線程池通常包含多個線程,用于執行網絡請求、文件讀寫、數據庫操作等耗時的 IO 操作。在 IO 線程池中執行 IO 操作時,我們可以使用掛起函數來等待異步操作的完成,而不需要阻塞主線程或其他重要線程。

例如,在下面的例子中,我們使用 Dispatchers.IO 調度器來將協程分配到 IO 線程池中執行網絡請求:

val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
    val response = fetchUser("123")
    // 處理響應結果
}
scope.cancel()

在上面的例子中,我們使用 launch 函數啟動了一個新的協程,并使用 Dispatchers.IO 調度器將其分配到 IO 線程池中執行。在協程中,我們使用 fetchUser 函數來發起網絡請求,并使用掛起函數來等待響應結果的返回。當響應結果返回后,協程會自動恢復執行,并將結果返回給調用方。

在 Kotlin 中,我們可以使用 CoroutineScope 接口來創建協程作用域,并在作用域內啟動協程。在創建協程作用域時,我們需要指定協程的上下文和調度器,以便管理協程的生命周期和執行。

  • 使用 GlobalScope GlobalScope 適用于一些簡單的、短時間的任務,例如發送一條日志、執行一個簡單的計算等。由于 GlobalScope 是一個全局的協程作用域,因此這種方式不適合長時間運行的任務,因為它可能會導致協程泄漏和線程泄漏的問題。
GlobalScope.launch {
    // 發送一條日志
    Log.d(TAG, "Hello, World!")
}
  • 使用 CoroutineScope CoroutineScope 適用于一些需要長時間運行的任務,例如網絡請求、文件讀寫、數據庫操作等。在創建協程作用域時,我們需要指定協程的上下文和調度器,以便管理協程的生命周期和執行。
val scope = CoroutineScope(Dispatchers.IO)
scope.launch {
    // 執行一個網絡請求
    val response = fetchUser("123")
    // 處理響應結果
}

在上面的例子中,我們使用 CoroutineScope 創建了一個局部的協程作用域,并使用 Dispatchers.IO 調度器將協程分配到 IO 線程池中執行。在協程中,我們使用 fetchUser 函數來發起網絡請求,并使用掛起函數來等待響應結果的返回。當響應結果返回后,協程會自動恢復執行,并將結果返回給調用方。

  • runBlocking runBlocking 適用于一些測試代碼,例如單元測試、集成測試等。在測試代碼中,我們通常需要啟動協程,并等待協程執行完成后進行斷言。
@Test
fun testFetchUser() = runBlocking {
    // 啟動一個協程
    val response = fetchUser("123")
    // 斷言響應結果
    assertEquals("John Doe", response.name)
}

在上面的例子中,我們使用 runBlocking 啟動了一個新的協程,并在協程中發起了一個網絡請求。由于這是一個測試代碼,因此我們可以使用 runBlocking 阻塞當前線程,直到協程執行完成后進行斷言。

  • lifecycleScope lifecycleScope 適用于一些需要與 Activity 或 Fragment 的生命周期綁定的任務,例如更新 UI 界面、執行后臺任務等。在使用 lifecycleScope 時,我們可以避免協程泄漏和線程泄漏的問題,并且可以自動取消協程,以便釋放資源。
class MyFragment : Fragment() {

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        lifecycleScope.launch {
            // 更新 UI 界面
            textView.text = "Hello, World!"
            // 執行后臺任務
            val response = fetchUser("123")
            // 處理響應結果
        }
    }
}

在上面的例子中,我們在 Fragment 的 onViewCreated 方法中使用 lifecycleScope 啟動了一個新的協程,并將其與 Fragment 的生命周期綁定。當 Fragment 被銷毀時,lifecycleScope 會自動取消協程,以便釋放資源。在協程中,我們可以更新 UI 界面、執行后臺任務等操作,而不需要擔心協程泄漏和線程泄漏的問題。

協程調度器

協程調度器是一種決定協程在哪個線程上運行的機制。在 Kotlin 中,協程調度器由 CoroutineDispatcher 接口表示。

常用的調度器如下

  • Dispatchers.Default:將協程分配到默認的線程池中執行。默認的線程池通常包含多個線程,用于執行 CPU 密集型的計算任務。
  • Dispatchers.IO:將協程分配到 IO 線程池中執行。IO 線程池通常包含多個線程,用于執行網絡請求、文件讀寫、數據庫操作等耗時的 IO 操作。
  • Dispatchers.Main:將協程分配到主線程中執行。主線程通常用于處理 UI 事件和更新 UI 界面。
  • Dispatchers.Unconfined:將協程分配到當前線程中執行,直到第一個掛起點。在第一個掛起點之后,協程會自動切換到其他線程或線程池中執行。

?除了上述常用的調度器之外,我們還可以自定義調度器,以便更好地滿足具體的需求。例如,我們可以使用 newSingleThreadContext 函數創建一個新的單線程調度器,用于將協程分配到單個線程中執行。

?

協程上下文

協程上下文是一組鍵值對,它包含了協程的一些屬性和配置信息。在 Kotlin 中,協程上下文由 CoroutineContext 接口表示。

在 Kotlin 協程中,協程上下文(Coroutine Context)是一個包含了協程執行所需的各種元素的對象。協程上下文可以包含多個元素,例如調度器、異常處理器、協程名稱等。在協程中,我們可以使用 coroutineContext 屬性來訪問當前協程的上下文。

以下是協程上下文中常用的元素:

  • Job:協程的任務,用于管理協程的生命周期和取消操作。
  • CoroutineDispatcher:協程的調度器,用于將協程分配到不同的線程或線程池中執行。
  • CoroutineExceptionHandler:協程的異常處理器,用于處理協程中發生的異常。
  • CoroutineName:協程的名稱,用于標識協程的作用和用途。

在協程中,我們可以使用 CoroutineScope 接口來創建協程作用域,并在作用域內啟動協程。在創建協程作用域時,我們可以指定協程的上下文和調度器,以便管理協程的生命周期和執行。

在協程中,我們可以使用 withContext 函數來切換協程的上下文和調度器。withContext 函數會掛起當前協程,并在指定的上下文和調度器中啟動一個新的協程。當新的協程執行完成后,withContext 函數會自動恢復當前協程的執行。

以下是使用 withContext 函數切換協程上下文的示例:

suspend fun fetchUser(id: String): User = withContext(Dispatchers.IO) {
    // 在 IO 線程池中執行網絡請求
    val response = apiService.fetchUser(id)
    // 解析響應結果
    val user = response.toUser()
    // 返回用戶信息
    user
}

在上面的例子中,我們使用 withContext函數將協程的上下文切換到 Dispatchers.IO 調度器中,并在 IO 線程池中執行網絡請求。當網絡請求完成后,withContext 函數會自動恢復當前協程的執行,并將解析后的用戶信息返回給調用方。

除了使用 withContext 函數切換協程上下文外,我們還可以使用 CoroutineScope 接口的擴展函數來切換協程上下文。以下是使用 CoroutineScope 接口的擴展函數切換協程上下文的示例:

val scope = CoroutineScope(Dispatchers.Main)
scope.launch {
    // 在主線程中執行 UI 操作
    textView.text = "Loading..."
    // 切換協程上下文到 IO 線程池中執行網絡請求
    val user = withContext(Dispatchers.IO) {
        apiService.fetchUser("123")
    }
    // 切換協程上下文到主線程中更新 UI 界面
    textView.text = "Hello, ${user.name}!"
}

在上面的例子中,我們使用 CoroutineScope 創建了一個局部的協程作用域,并將其與主線程的調度器綁定。在協程中,我們使用 withContext 函數將協程的上下文切換到 IO 線程池中執行網絡請求。當網絡請求完成后,我們再次使用 withContext 函數將協程的上下文切換回主線程中更新 UI 界面。

最后

這篇文章主要介紹了協程的概念,協程的掛起函數,作用域,調度器和上下文,更多文章可以關注公眾號QStack。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 操作系統
    +關注

    關注

    37

    文章

    6747

    瀏覽量

    123201
  • 線程
    +關注

    關注

    0

    文章

    504

    瀏覽量

    19653
  • kotlin
    +關注

    關注

    0

    文章

    60

    瀏覽量

    4185
收藏 人收藏

    評論

    相關推薦

    談談的那些事兒

    隨著異步編程的發展以及各種并發框架的普及,作為一種異步編程規范在各類語言中地位逐步提高。我們不單單會在自己的程序中使用,各類框架如fastapi,aiohttp等也都是基于異步
    的頭像 發表于 01-26 11:36 ?1092次閱讀
    談談<b class='flag-5'>協</b><b class='flag-5'>程</b>的那些事兒

    和線程有什么區別

    和線程的區別和線程的共同目的之一是實現系統資源的上下文調用,不過它們的實現層級不同;線程(Thraed)是比進程小一級的的運行單位,多線程實現系統資源上下文調用,是編程語言交付
    發表于 12-10 06:23

    什么是多任務系統?FreeRTOS任務與簡析

    功能,初學者必須先掌握——任務的創建、刪除、掛起和恢復等操作。本章節分為如下幾部分:*什么是多任務系統*FreeRTOS任務與*初次使用*任務狀態*任務優先級*任務實現*任務控制塊*任務堆棧一、什么是多任務系統單片機一般都是
    發表于 02-18 06:38

    關于C++ 20最全面詳解

    花了一兩周的時間后,我想寫寫 C++20 的基本用法,因為 C++ 的讓我感到很奇怪,寫一個
    的頭像 發表于 04-12 11:10 ?1.3w次閱讀
    關于C++ 20<b class='flag-5'>協</b><b class='flag-5'>程</b>最全面詳解

    Python后端項目的是什么

    最近公司 Python 后端項目進行重構,整個后端邏輯基本都變更為采用“異步”的方式實現。看著滿屏幕經過 async await(在 Python 中的實現)修飾的代碼,我頓時
    的頭像 發表于 09-23 14:38 ?1308次閱讀

    Python與JavaScript的對比及經驗技巧

    前言以前沒怎么接觸前端,對 JavaScript 的異步操作不了解,現在有了點了解。一查發現 Python 和 JavaScript 的發展史簡直就是一毛一樣!這里大致做下橫向對比和總結,便于
    的頭像 發表于 10-20 14:30 ?1890次閱讀

    通過例子由淺入深的理解yield

    send:send() 方法致使程前進到下一個yield 語句,另外,生成器可以作為使用
    的頭像 發表于 08-23 11:12 ?2000次閱讀

    使用channel控制數量

    goroutine 是輕量級線程,調度由 Go 運行時進行管理的。Go 語言的并發控制主要使用關鍵字 go 開啟 goroutine。Go (Goroutine)之間通過信道(
    的頭像 發表于 09-19 15:06 ?1117次閱讀

    詳解Linux線程、線程與異步編程、與異步

    不是系統級線程,很多時候被稱為“輕量級線程”、“微線程”、“纖(fiber)”等。簡單來說可以認為
    的頭像 發表于 03-16 15:49 ?950次閱讀

    Kotlin實戰進階之筑基篇3

    概念在1958年就開始出現(比線程還早), 目前很多語言開始原生支, Java 沒有原生但是大型公司都自己或者使用第三方庫來支持
    的頭像 發表于 05-30 16:26 ?672次閱讀

    FreeRTOS任務與介紹

    FreeRTOS 中應用既可以使用任務,也可以使用(Co-Routine),或者兩者混合使用。但是任務和協使用不同的API函數,因此不能通過隊列(或信號量)將數據從任務發送給
    的頭像 發表于 09-28 11:02 ?951次閱讀

    的作用、結構及原理

    本文介紹的作用、結構、原理,并使用C++和匯編實現了64位系統下的池。文章內容避免了
    的頭像 發表于 11-08 16:39 ?1073次閱讀
    <b class='flag-5'>協</b><b class='flag-5'>程</b>的作用、結構及原理

    C/C++編程的相關概念和技巧

    自己的寄存器上下文和棧,可以在多個入口點間自由切換,而不是像傳統的函數調用那樣在一個入口點開始、另一個入口點結束。概念最早可以追溯到1963年,由Melvin Conway提出。
    的頭像 發表于 11-09 11:34 ?721次閱讀

    的實現與原理

    前言 這個概念很久了,好多程序員是實現過這個組件的,網上關于的文章,博客,論壇都是汗牛充棟,在知乎,github上面也有很多大牛寫了
    的頭像 發表于 11-10 10:57 ?415次閱讀

    Linux線程、線程與異步編程、與異步介紹

    不是系統級線程,很多時候被稱為“輕量級線程”、“微線程”、“纖(fiber)”等。簡單來說可以認為
    的頭像 發表于 11-11 11:35 ?1053次閱讀
    Linux線程、線程與異步編程、<b class='flag-5'>協</b><b class='flag-5'>程</b>與異步<b class='flag-5'>介紹</b>