那曲檬骨新材料有限公司

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

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

3天內不再提示

Kotlin協程實戰進階之筑基篇1

jf_78858299 ? 來源:小余的自習室 ? 作者:蘇火火 ? 2023-05-30 16:24 ? 次閱讀

前言

公司開啟新項目了,想著準備亮一手 Kotlin 協程應用到項目中去,之前有對 Kotlin 協程的知識進行一定量的學習,以為自己理解協程了,結果……實在拿不出手!

為了更好的加深記憶和理解,更全面系統深入地學習 Kotlin 協程的知識,協程將分為三部分來講解,本文是第一篇

一、概述

協程的概念在1958年就開始出現(比線程還早), 目前很多語言開始原生支, Java 沒有原生協程但是大型公司都自己或者使用第三方庫來支持協程編程, 但是Kotlin原生支持協程。

Android 中的每個應用都會運行一個主線程,它主要是用來處理 UI,如果主線程上需要處理的任務太多,應用就感覺被卡主一樣影響用戶體驗,得讓那些耗時的任務不阻塞主線程的運行。要做到處理網絡請求不會阻塞主線程,一個常用的做法就是使用回調,另一種是使用協程。

協程概念

很多人都會問協程是什么?這里引用官方的解釋:

1.協程通過將復雜性放入庫來簡化異步編程。程序的邏輯可以在協程中順序地表達,而底層庫會為我們解決其異步性。該庫可以將用戶代碼的相關部分包裝為回調、訂閱相關事件、在不同線程(甚至不同機器)上調度執行,而代碼則保持如同順序執行一樣簡單。

2.協程是一種并發設計模式。

協程就像輕量級的線程,為什么是輕量的?因為協程是依賴于線程,一個線程中可以創建N個協程, 很重要的一點就是協程掛起時不會阻塞線程 ,幾乎是無代價的。而且它 基于線程池API ,所以在處理并發任務這件事上它真的游刃有余。

協程只是一種概念,它提供了一種避免阻塞線程并用更簡單、更可控的操作替代線程阻塞的方法: 協程掛起和恢復本質上Kotlin協程就是作為在Kotlin語言上進行異步編程的解決方案,處理異步代碼的方法 。

有可能有的同學問了,既然它基于線程池,那我直接使用線程池或者使用 Android 中其他的異步任務解決方案,比如 Handler、AsyncTask、RxJava等,不更好嗎?

協程可以 使用阻塞的方式寫出非阻塞式的代碼 ,解決并發中常見的回調地獄。消除了并發任務之間的協作的難度,協程可以讓我們輕松地寫出復雜的并發代碼。一些本來不可能實現的并發任務變的可能,甚至簡單,這些才是協程的優勢所在。

作用

  • 1.協程可以讓異步代碼同步化
  • 2.協程可以降低異步程序的設計復雜度 。

特點

  • 輕量 :您可以在單個線程上運行多個協程,因為協程支持掛起,不會使正在運行協程的線程阻塞。掛起比阻塞節省內存,且支持多個并行操作。
  • 內存泄漏更少 :使用結構化并發機制在一個作用域內執行多項操作。
  • 內置取消支持 :取消操作會自動在運行中的整個協程層次結構內傳播。
  • Jetpack 集成 :許多 Jetpack 庫都包含提供全面協程支持的擴展。某些庫還提供自己的協程作用域,可供您用于結構化并發。

Kotlin Coroutine 生態

圖片

kotlin的協程實現分為了兩個層次:

  • 基礎設施層 :標準庫的協程API,主要對協程提供了概念和語義上最基本的支持;
  • 業務框架層 kotlin.coroutines :協程的上層框架支持,基于標準庫實現的封裝,也是我們日常開發使用的協程擴展庫。

依賴庫

projectgradle 添加 Kotlin 編譯插件:

dependencies {
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.4.32"
}

要使用協程,還需要在app的 build.gradle 文件中添加依賴:

dependencies {
     //協程標準庫
    implementation "org.jetbrains.kotlin:kotlin-stdlib:1.4.32"
    //協程核心庫
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
    //協程Android支持庫,提供安卓UI調度器
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
}

這里我們主要使用協程擴展庫, kotlin協程標準庫太過于簡陋不適用于開發者使用。

二、原理

協程的概念最核心的點就是函數或者一段程序能夠被掛起,稍后再在掛起的位置恢復 。協程通過主動讓出運行權來實現協作,程序自己處理掛起和恢復來實現程序執行流程的協作調度。因此它本質上就是在討論程序控制流程的機制。

使用場景

kotlin協程基于Thread相關API的封裝,讓我們不用過多關心線程也可以方便地寫出并發操作,這就是Kotlin的協程。協程的好處本質上和其他線程api一樣, 方便 。

在 Android 平臺上,協程有兩個主要使用場景:

  • 1、線程切換,保證線程安全。
  • 2、處理耗時任務(比如網絡請求、解析JSON數據、從數據庫中進行讀寫操作等)。

Kotlin協程的原理

我們使用 Retrofit 發起了一個異步請求,從服務端查詢用戶的信息,通過 CallBack 返回 response

val call: Call

很明顯我們需要處理很多的回調分支,如果業務多則更容易陷入「回調地獄」繁瑣凌亂的代碼中。

使用協程,同樣可以像 Rx 那樣有效地消除回調地獄,不過無論是設計理念,還是代碼風格,兩者是有很大區別的,協程在寫法上和普通的順序代碼類似,同步的方式去編寫異步執行的代碼。使用協程改造后代碼如下:

GlobalScope.launch(Dispatchers.Main) {//開始協程:主線程
    val result = userApi.getUserSuspend("suming")//網絡請求(IO 線程)
    tv_name.text = result?.name //更新 UI(主線程)
}

這就是kotlin最有名的【非阻塞式掛起】,使用同步的方式完成異步任務,而且很簡潔,這是Kotlin協程的魅力所在。之所有可以用看起來同步的方式寫異步代碼,關鍵在于請求函數getUserSuspend()是一個 掛起函數 ,被suspend關鍵字修飾,下面會介紹。

圖片

在上面的協程的原理圖解中,耗時阻塞的操作并沒有減少,只是交給了其他線程。userApi.getUserSuspend("suming")真正執行的時候會切換到IO線程中執行,獲取結果后最后恢復到主線程上,然后繼續執行剩下的流程。

將業務流程原理拆分得更細致一點,在主線程中創建協程A中執行整個業務流程,如果遇到異步調用任務則協程A被掛起,切換到IO線程中創建子協程B,獲取結果后再恢復到主線程的協程A上,然后繼續執行剩下的流程。

圖片

協程Coroutine雖然不能脫離線程而運行,但可以在不同的線程之間切換,而且一個線程上可以一個或多個協程。下圖動態顯示了進程 - 線程 - 協程微妙關系。

圖片

此動圖來源

三、基礎

GlobalScope.launch(Dispatchers.Main) {//開始協程:主線程
   val result = userApi.getUserSuspend("suming")//網絡請求(IO 線程)
   tv_name.text = result?.name //更新 UI(主線程)
}

上面就是啟動協程的代碼,啟動協程的代碼可以分為三部分:GlobalScopelaunch、Dispatchers,它們分別對應:協程的作用域、構建器和調度器。

1.協程的構建

上面的GlobalScope.launch()屬于協程構建器Coroutine builders,Kotlin 中還有其他幾種 Builders, 負責創建協程

  • runBlocking:T:頂層函數,創建一個新的協程同時阻塞當前線程,直到其內部所有邏輯以及子協程所有邏輯全部執行完成,返回值是泛型T,一般在項目中不會使用,主要是為main函數和測試設計的。
  • launch:?創建一個新的協程,不會阻塞當前線程,必須在協程作用域中才可以調用。它返回的是一個該協程任務的引用,即Job對象。這是最常用的用于啟動協程的方式。
  • async:?創建一個新的協程,不會阻塞當前線程,必須在協程作用域中才可以調用。并返回Deffer對象,可通過調用Deffer.await()方法等待該子協程執行完成并獲取結果。常用于并發執行-同步等待和獲取返回值的情況。

runBlocking

fun  runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
  • context:??協程的上下文,表示協程的運行環境,包括協程調度器、代表協程本身的Job、協程名稱、協程ID等,默認值是當前線程上的事件循環。(這里的context和Android的context不同,后面會講解到)
  • block:???協程執行體,是一個用suspend關鍵字修飾的一個無參,無返回值的函數類型。是一個帶接收者的函數字面量,接收者是 CoroutineScope ,因此執行體包含了一個隱式的 CoroutineScope,所以在 runBlocking 內部可以來直接啟動協程。
  • T:?????返回值是泛型T,協程體block中最后一行返回的是什么類型T就是什么類型。

它是一個頂層函數,不是GlobalScope的 API,可以在任意地方獨立使用。它能創建一個新的協程同時阻塞當前線程,直到其內部所有邏輯以及子協程所有邏輯全部執行完成,它的目的是將常規的阻塞代碼與以掛起suspend風格編寫的庫連接起來,常用于main函數和測試中。一般我們在項目中是不會使用的。

fun runBloTest() {
    print("start")
    //context上下文使用默認值,阻塞當前線程,直到代碼塊中的邏輯完成
    runBlocking {
        //這里是協程體
        delay(1000)//掛起函數,延遲1000毫秒
        print("runBlocking")
    }
    print("end")
}

打印數據如下:

圖片

runBlocking.gif

只有在runBlocking協程體邏輯全部運行結束后,聲明在runBlocking之后的代碼才能執行,即runBlocking會阻塞其所在線程。

注意:runBlocking 雖然會阻塞當前線程的,但其內部運行的協程又是非阻塞的。

launch

launch是最常用的用于啟動協程的方式,用于在不阻塞當前線程的情況下啟動一個協程,并返回對該協程任務的引用,即Job對象。

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job
  • context:?協程的上下文,表示協程的運行環境,包括協程調度器、代表協程本身的Job、協程名稱、協程ID等,默認值是當前線程上的事件循環。
  • start: ?協程啟動模式,這些啟動模式的設計主要是為了應對某些特殊的場景。業務開發實踐中通常使用DEFAULTLAZY這兩個啟動模式就夠了。
  • block:??協程代碼,它將在提供的范圍的上下文中被調用。它是一個用suspend(掛起函數)關鍵字修飾的一個無參,無返回值的函數類型。接收者是CoroutineScope的函數字面量。
  • Job:???協程構建函數的返回值,可以把Job看成協程對象本身,封裝了協程中需要執行的代碼邏輯,是協程的唯一標識,Job可以取消,并且負責管理協程的生命周期。

協程需要運行在協程上下文環境中 (即協程作用域,下面會講解到),在非協程環境中launch有兩種方式創建協程:

GlobalScope.launch()

在應用范圍內啟動一個新協程,不會阻塞調用線程,協程的生命周期與應用程序一致。表示一個不綁定任何Job的全局作用域,用于啟動頂層協程,這些協程在整個應用程序生命周期中運行,不會提前取消(不存在Job)。

fun launchTest() {
    print("start")
    //創建一個全局作用域協程,不會阻塞當前線程,生命周期與應用程序一致
    GlobalScope.launch {
        //在這1000毫秒內該協程所處的線程不會阻塞
        //協程將線程的執行權交出去,該線程繼續干它要干的事情,到時間后會恢復至此繼續向下執行
        delay(1000)//1秒無阻塞延遲(默認單位為毫秒)
        print("GlobalScope.launch")
    }
    print("end")//主線程繼續,而協程被延遲
}

GlobalScope.launch()協程將線程的執行權交出去,該線程繼續干它要干的事情,主線程繼續,而協程被延遲,到時間后會恢復至此繼續向下執行。

打印數據如下:

圖片

launch1.gif

由于這樣啟動的協程存在組件已被銷毀但協程還存在的情況,極限情況下可能導致資源耗盡,尤其是在 Android 客戶端這種需要頻繁創建銷毀組件的場景,因此不推薦這種用法。

注意:這里說的是GlobalScope沒有Job, 但是啟動的launch是有Job的。 GlobalScope本身就是一個作用域, launch屬于其子作用域。

CoroutineScope.launch()

啟動一個新的協程而不阻塞當前線程,并返回對協程的引用作為一個Job。通過CoroutineContext至少一個協程上下文參數創建一個 CoroutineScope對象。協程上下文控制協程生命周期和線程調度,使得協程和該組件生命周期綁定,組件銷毀時,協程一并銷毀,從而實現安全可靠地協程調用。這是在應用中最推薦使用的協程使用方式。

fun launchTest2() {
    print("start")
    //開啟一個IO模式的協程,通過協程上下文創建一個CoroutineScope對象,需要一個類型為CoroutineContext的參數
    val job = CoroutineScope(Dispatchers.IO).launch {
        delay(1000)//1秒無阻塞延遲(默認單位為毫秒)
        print("CoroutineScope.launch")
    }
    print("end")//主線程繼續,而協程被延遲
}
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • Android
    +關注

    關注

    12

    文章

    3945

    瀏覽量

    127947
  • JAVA
    +關注

    關注

    19

    文章

    2975

    瀏覽量

    105156
  • ui
    ui
    +關注

    關注

    0

    文章

    204

    瀏覽量

    21419
  • kotlin
    +關注

    關注

    0

    文章

    60

    瀏覽量

    4211
收藏 人收藏

    評論

    相關推薦

    談談的那些事兒

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

    M1 Mac開發Android遇到的坑與解決方法

    ,怎么辦?Q:XML文件無法預覽,怎么辦?Q:kotlin coroutines庫無法使用?完事前言我的第一“爆款文章”還是用我的第一臺白蘋果電腦作為主角寫的,Macbook p
    發表于 09-15 07:44

    Python中的多核CPU共享數據詳解

    又稱微線程,coroutne,是一種用戶態的輕量級線程。通俗點講就是周末我在家里休息,假如我先洗漱,再煮飯,再下載電影看會很慢,用了
    的頭像 發表于 12-07 10:23 ?6693次閱讀
    Python中的多核CPU共享數據<b class='flag-5'>之</b><b class='flag-5'>協</b><b class='flag-5'>程</b>詳解

    Python自動化運維函數賦值過程

    的優點:(1)無需線程上下文切換的開銷,避免了無意義的調度,由此可以提高性能(但也因此,程序員必須自己承擔調度的責任,同時,
    的頭像 發表于 03-18 11:22 ?3769次閱讀

    Python后端項目的是什么

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

    Python與JavaScript的對比及經驗技巧

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

    使用channel控制數量

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

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

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

    的概念及的掛起函數介紹

    是一種輕量級的線程,它可以在單個線程中實現并發執行。與線程不同,不需要操作系統的上下文切換,因此可以更高效地使用系統資源。Kotlin
    的頭像 發表于 04-19 10:20 ?942次閱讀

    Kotlin實戰進階2

    的概念在1958年就開始出現(比線程還早), 目前很多語言開始原生支, Java 沒有原生但是大型公司都自己或者使用第三方庫來支持
    的頭像 發表于 05-30 16:25 ?806次閱讀
    <b class='flag-5'>Kotlin</b><b class='flag-5'>協</b><b class='flag-5'>程</b><b class='flag-5'>實戰</b><b class='flag-5'>進階</b><b class='flag-5'>之</b><b class='flag-5'>筑</b><b class='flag-5'>基</b><b class='flag-5'>篇</b>2

    Kotlin實戰進階3

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

    FreeRTOS任務與介紹

    。 是為那些資源很少的 MCU 準備的,其開銷很小,但是 FreeRTOS 官方已經不打算再更新了。 任務特性: 1、簡單。 2、沒
    的頭像 發表于 09-28 11:02 ?1040次閱讀

    C++20無棧超輕量高性能異步庫開發實戰

    來了,c++標準委員會的謹慎態度也造就了c++20的給出來:“性能優秀”,“開發靈活”和讓人勸退的“門檻之高”。 不過話說回來,c++從出身就注定了背負性能使命,他不是為簡單為
    的頭像 發表于 11-09 10:20 ?1491次閱讀

    的實現與原理

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

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

    不是系統級線程,很多時候被稱為“輕量級線程”、“微線程”、“纖(fiber)”等。簡單來說可以認為
    的頭像 發表于 11-11 11:35 ?1279次閱讀
    Linux線程、線程與異步編程、<b class='flag-5'>協</b><b class='flag-5'>程</b>與異步介紹
    联兴棋牌| 真人百家乐官网博弈| 金沙百家乐现金网| 大发888 zhidu| 红宝石百家乐官网娱乐城| 百家乐补第三张牌规则| 尊尚会娱乐城| 澳门百家乐官网手机软件| 大发888娱乐场下载新澳博| 百家乐官网2号程序| 新葡京百家乐的玩法技巧和规则 | 松阳县| 百家乐投注信用最好的| 网上在线赌场| 百家乐洗码全讯网| 轮盘赌| 澳门百家乐要注意啥| 呼和浩特市| 赌场百家乐代理| 威尼斯人娱乐城返佣| 百家乐官网轮盘桌| 广州百家乐娱乐场开户注册| 百家乐官网视频双扣游戏| 百家乐java| 百家乐官网投注技巧公式| 沙龙百家乐代理| 金海岸百家乐官网娱乐城| 百家乐娱乐网网| 百家乐官网二路珠无敌稳赢打法| 百家乐天下| 迷你百家乐官网的玩法技巧和规则 | 博狗足球开户| 百家乐澳门路规则| 百家乐官网案件讯问| 百家乐北京| 网上赌场| 网络百家乐公式打法| 澳门百家乐官网官网www.bjbj100.com | 网络百家乐官网公式打法| 大发888wf娱乐场下载| 线上百家乐可靠吗|