RM新时代网站-首页

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

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

3天內不再提示

協(xié)程的概念及協(xié)程的掛起函數介紹

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

什么是協(xié)程

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

相關的基本概念

掛起函數

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

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

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

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

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

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

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

協(xié)程作用域

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

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

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

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()

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

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

?

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

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

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

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

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

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

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

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

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

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

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

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

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

協(xié)程調度器

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

常用的調度器如下

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

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

?

協(xié)程上下文

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

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

以下是協(xié)程上下文中常用的元素:

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

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

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

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

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

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

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

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

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

最后

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

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

    關注

    37

    文章

    6801

    瀏覽量

    123283
  • 線程
    +關注

    關注

    0

    文章

    504

    瀏覽量

    19675
  • kotlin
    +關注

    關注

    0

    文章

    60

    瀏覽量

    4187
收藏 人收藏

    評論

    相關推薦

    談談協(xié)的那些事兒

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

    協(xié)和線程有什么區(qū)別

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

    什么是多任務系統(tǒng)?FreeRTOS任務與協(xié)簡析

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

    關于C++ 20協(xié)最全面詳解

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

    Python后端項目的協(xié)是什么

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

    Python協(xié)與JavaScript協(xié)的對比及經驗技巧

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

    通過例子由淺入深的理解yield協(xié)

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

    使用channel控制協(xié)數量

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

    詳解Linux線程、線程與異步編程、協(xié)與異步

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

    Kotlin協(xié)實戰(zhàn)進階之筑基篇3

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

    FreeRTOS任務與協(xié)介紹

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

    協(xié)的作用、結構及原理

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

    C/C++協(xié)編程的相關概念和技巧

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

    協(xié)的實現與原理

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

    Linux線程、線程與異步編程、協(xié)與異步介紹

    協(xié)不是系統(tǒng)級線程,很多時候協(xié)被稱為“輕量級線程”、“微線程”、“纖(fiber)”等。簡單來說可以認為
    的頭像 發(fā)表于 11-11 11:35 ?1138次閱讀
    Linux線程、線程與異步編程、<b class='flag-5'>協(xié)</b><b class='flag-5'>程</b>與異步<b class='flag-5'>介紹</b>
    RM新时代网站-首页