在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

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

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

3天內不再提示

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

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

5.協程上下文

CoroutineContext表示協程上下文,是 Kotlin 協程的一個基本結構單元。協程上下文主要承載著資源獲取,配置管理等工作,是執行環境的通用數據資源的統一管理者。它有很多作用,包括攜帶參數,攔截協程執行等等。如何運用協程上下文是至關重要的,以此來實現正確的線程行為、生命周期、異常以及調試。

協程使用以下幾種元素集定義協程的行為,它們均繼承自CoroutineContext

  • Job:??????????協程的句柄,對協程的控制和管理生命周期。
  • CoroutineName:??????協程的名稱,可用于調試。
  • CoroutineDispatcher:???調度器,確定協程在指定的線程來執行。
  • CoroutineExceptionHandler:協程異常處理器,處理未捕獲的異常。這里暫不做深入分析,后面的文章會講解到,敬請期待。

協程上下文的數據結構特征更加顯著,與List和Map非常類似。它包含用戶定義的一些數據集合,這些數據與協程密切相關。它是一個有索引Element 實例集合。每個 element 在這個集合有一個唯一的Key

//協程的持久上下文。它是[Element]實例的索引集,這個集合中的每個元素都有一個唯一的[Key]。
public interface CoroutineContext {
    //從這個上下文中返回帶有給定[key]的元素或null。
    public operator fun  get(key: Key<E>): E?

    //從[initial]值開始累加該上下文的項,并從左到右應用[operation]到當前累加器值和該上下文的每個元素。
    public fun  fold(initial: R, operation: (R, Element) -> R): R

    //返回一個上下文,包含來自這個上下文的元素和來自其他[context]的元素。
    public operator fun plus(context: CoroutineContext): CoroutineContext

    //返回一個包含來自該上下文的元素的上下文,但不包含指定的[key]元素。
    public fun minusKey(key: Key<*>): CoroutineContext

    //[CoroutineContext]元素的鍵。[E]是帶有這個鍵的元素類型。
    public interface Key<E : Element>

    //[CoroutineContext]的一個元素。協程上下文的一個元素本身就是一個單例上下文。
    public interface Element : CoroutineContext {
        //這個協程上下文元素的key
        public val key: Key<*>

        public override operator fun  get(key: Key<E>): E?
    }
}
  • get(key):?可以通過key從這個上下文中獲取這個Element元素或者null
  • fold():????提供遍歷當前上下文中所有元素的能力。
  • plus(context):?顧名思義它是一個加法運算,多個上下文元素可以通過+的形式整合成一個上下文返回。
  • minusKey(key):?與plus相反,減法運算,刪除當前上下文中指定key的元素,返回的是不包含指定
  • Element:????協程上下文的一個元素,本身就是一個單例上下文,里面有一個key,是這個元素的索引。

Element本身也實現了CoroutineContext 接口,像Int實現了List一樣,為什么元素本身也是集合呢?主要是Element它不會存放除它自己以外的數據;Element屬性又有一個key,是協程上下文這個集合中元素的索引。這個索引在元素里面,說明元素一產生就找到自己的位置。

注意:協程上下文的內部實現實際是一個單鏈表。

CoroutineName

//用戶指定的協程名稱。此名稱用于調試模式。
public data class CoroutineName(
    //定義協程的名字
    val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
    //CoroutineName實例在協程上下文中的key
    public companion object Key : CoroutineContext.Key<CoroutineName>
}

CoroutineName是用戶用來指定的協程名稱的,用于方便調試和定位問題:

GlobalScope.launch(CoroutineName("GlobalScope")) {
    launch(CoroutineName("CoroutineA")) {//指定協程名稱
        val coroutineName = coroutineContext[CoroutineName]//獲取協程名稱
        print(coroutineName)
    }
}

協程內部可以通過coroutineContext這個全局屬性直接獲取當前協程的上下文。打印數據如下:

kotlin
復制代碼[DefaultDispatcher-worker-2] CoroutineName(CoroutineA)

上下文組合

從上面的協程創建的函數中可以看到,協程上下文的參數只有一個,但是怎么傳遞多個上下文元素呢?CoroutineContext可以使用 " + " 運算符進行合并。由于CoroutineContext是由一組元素組成的,所以加號右側的元素會覆蓋加號左側的元素,進而組成新創建的CoroutineContext

GlobalScope.launch {
    //通過+號運算添加多個上下文元素
    var context = CoroutineName("協程1") + Dispatchers.Main
    print("context == $context")

    context += Dispatchers.IO //添加重復Dispatchers元素,Dispatchers.IO 會替換 ispatchers.Main
    print("context == $context")

    val contextResult = context.minusKey(context[CoroutineName]!!.key)//移除CoroutineName元素
    print("contextResult == $contextResult")
}

注意:如果有重復的元素(key一致)則會右邊的會代替左邊的元素。打印數據如下:

context == [CoroutineName(協程1), Dispatchers.Main]
context == [CoroutineName(協程1), Dispatchers.IO]
contextResult == Dispatchers.IO

6.啟動模式

CoroutineStart是一個枚舉類,為協程構建器定義啟動選項。在協程構建的start參數中使用,

啟動模式 含義 說明
DEFAULT 默認啟動模式,立即根據它的上下文調度協程的執行 是立即調度,不是立即執行,DEFAULT是餓漢式啟動,launch調用后,會立即進入待調度狀態,一旦調度器 OK 就可以開始執行。如果協程在執行前被取消,其將直接進入取消響應的狀態。
LAZY 懶啟動模式,啟動后并不會有任何調度行為,直到我們需要它執行的時候才會產生調度 包括主動調用該協程的startjoin或者await等函數時才會開始調度,如果調度前就被取消,協程將直接進入異常結束狀態。
ATOMIC 類似[DEFAULT],以一種不可取消的方式調度協程的執行 雖然是立即調度,但其將調度和執行兩個步驟合二為一了,就像它的名字一樣,其保證調度和執行是原子操作,因此協程也一定會執行。
UNDISPATCHED 類似[ATOMIC],立即執行協程,直到它在當前線程中的第一個掛起點。 是立即執行,因此協程一定會執行。即使協程已經被取消,它也會開始執行,但不同之處在于它在同一個線程中開始執行。

這些啟動模式的設計主要是為了應對某些特殊的場景。業務開發實踐中通常使用DEFAULTLAZY這兩個啟動模式就夠了。

7.suspend 掛起函數

suspend 是 Kotlin 協程最核心的關鍵字,使用suspend關鍵字修飾的函數叫作掛起函數掛起函數只能在協程體內或者在其他掛起函數內調用。否則 IDE 就會提示一個錯誤:

Suspend function 'xxxx' should be called only from a coroutine or another suspend function

協程提供了一種避免阻塞線程并用更簡單、更可控的操作替代線程阻塞的方法:協程掛起和恢復 。協程在執行到有suspend標記的函數時,當前函數會被掛起(暫停),直到該掛起函數內部邏輯完成,才會在掛起的地方resume恢復繼續執行。

本質上,掛起函數就是一個提醒作用,函數創建者給函數調用者的提醒,表示這是一個比較耗時的任務,被創建者用suspend標記函數,調用者只需把掛起函數放在協程里面,協程會自動調度處理,完成后在原來的位置恢復執行。

注意:協程會在主線程中運行,suspend 并不代表后臺執行。

如果需要處理一個函數,且這個函數在主線程上執行太耗時,但是又要保證這個函數是主線程安全的,那么您可以讓 Kotlin 協程在 Default 或 IO 調度器上執行工作。在 Kotlin 中,所有協程都必須在調度器中運行,即使它們是在主線程上運行也是如此。協程可以 自行掛起(暫停) ,而調度器負責將其 恢復

掛起點

協程內部掛起函數調用的地方稱為掛起點 ,或者有下面這個標識的表示這個就是掛起點。

掛起和恢復

協程在常規函數的基礎上添加了suspendresume兩項操作用于處理長時間運行的任務:

  • suspend:也稱掛起或暫停,用于掛起(暫停)執行當前協程,并保存所有局部變量。
  • resume:恢復,用于讓已掛起(暫停)的協程從掛起(暫停)處恢復繼續執行。

Kotlin 使用堆棧幀來管理要運行哪個函數以及所有局部變量。 掛起 (暫停)協程時,會復制并保存當前的堆棧幀以供稍后使用,將信息保存到Continuation對象中。恢復協程時,會將堆棧幀從其保存位置復制回來,對應的Continuation通過調用resumeWith函數才會恢復協程的執行,然后函數再次開始運行。同時返回Result類型的成功或者異常的結果。

//Continuation接口表示掛起點之后的延續,該掛起點返回類型為“T”的值。
public interface Continuation<in T> {
    //對應這個Continuation的協程上下文
    public val context: CoroutineContext

    //恢復相應協程的執行,傳遞一個成功或失敗的結果作為最后一個掛起點的返回值。
    public fun resumeWith(result: Result<T>)
}

//將[value]作為最后一個掛起點的返回值,恢復相應協程的執行。
fun  Continuation.resume(value: T): Unit =
    resumeWith(Result.success(value))

//恢復相應協程的執行,以便在最后一個掛起點之后重新拋出[異常]。
fun  Continuation.resumeWithException(exception: Throwable): Unit =
    resumeWith(Result.failure(exception))

Kotlin 的 Continuation 類有一個 resumeWith 函數可以接收 Result 類型的參數。在結果成功獲取時,調用resumeWith(Result.success(value))或者調用拓展函數resume(value);出現異常時,調用resumeWith(Result.failure(exception))或者調用拓展函數resumeWithException(exception),這就是 Continuation 的恢復調用。

Continuation類似于網絡請求回調Callback,也是一個請求成功和一個請求失敗的回調:

public interface Callback {
  //請求失敗回調
  void onFailure(Call call, IOException e);

  //請求成功回調
  void onResponse(Call call, Response response) throws IOException;
}

注意:suspend不一定真的會掛起,如果只是提供了掛起的條件,但是協程沒有產生異步調用,那么協程還是不會被掛起。

那么協程是如何做到掛起和恢復?

suspend本質(奪命七步)

一個掛起函數要掛起,那么它必定得有一個掛起點,不然無法知道函數是否掛起,從哪掛起呢?

@GET("users/{login}")
suspend fun getUserSuspend(@Path("login") login: String): User

第一步 :將上面的掛起函數解析成字節碼:通過AS的工具欄中Tools->kotlin->show kotlin ByteCode

kotlin
復制代碼public abstract getUserSuspend(Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;

上面的掛起函數本質是這樣的,你會發現多了一個參數,這個參數就是Continuation,也就是說調用掛起函數的時候需要傳遞一個Continuation給它,只是傳遞這個參數是由編譯器悄悄傳,而不是我們傳遞的。這就是掛起函數為什么只能在協程或者其他掛起函數中執行,因為只有掛起函數或者協程中才有Continuation

第二步 :這里的Continuation參數,其實它類似CallBack回調函數,resumeWith()就是成功或者失敗回調的結果:

public interface Continuation<in T> {
    //協程上下文
    public val context: CoroutineContext

    //恢復相應協程的執行,傳遞一個成功或失敗的[result]作為最后一個掛起點的返回值。
    public fun resumeWith(result: Result<T>)
}

第三步 :但是它是從哪里傳進來的呢?這個函數只能在協程或者掛起函數中執行,說明Continuation很有可能是從協程充傳入來的,查看協程構建的源碼:

public fun CoroutineScope.launch(): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

第四步 :通過launch啟動一個協程的時候,他通過coroutinestart方法啟動協程:

public fun  start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
    initParentJob()
    start(block, receiver, this)
}

第五步 :然后start方法里面調用了CoroutineStartinvoke,這個時候我們發現了Continuation:

public operator fun  invoke(block: suspend () -> T, completion: Continuation<T>): Unit =
    when (this) {
        DEFAULT -> block.startCoroutineCancellable(completion)
        ATOMIC -> block.startCoroutine(completion)
        UNDISPATCHED -> block.startCoroutineUndispatched(completion)
        LAZY -> Unit // will start lazily
    }

第六步 :而 Continuation通過block.startCoroutine(completion)傳入:

public fun  (suspend () -> T).startCoroutine(completion: Continuation

第七步 :最終回調到上面ContinuationresumeWith()恢復函數里面。這里可以看出協程體本身就是一個Continuation,這也就解釋了為什么必須要在協程內調用suspend掛起函數了。(由于篇幅原因這里不做深入分析,后續的文章會分析這里,敬請期待!)

額外知識點:在創建協程的底層源碼中,創建協程會返回一個Continuation實例,這個實例就是套了幾層馬甲的協程體,調用它的resume可以觸發協程的執行。

任何一個協程體或者掛起函數中都隱含有一個Continuation實例,編譯器能夠對這個實例進行正確的傳遞,并將這個細節隱藏在協程的背后,讓我們的異步代碼看起來像同步代碼一樣。

@GET("users/{login}")
suspend fun getUserSuspend(@Path("login") login: String): User

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

launch()創建的這個協程,在執行到某一個suspend掛起函數的時候,這個協程會被掛起,從當前線程掛起。也就是說這個協程從正在執行它的線程上脫離,這個協程在掛起函數指定的線程上繼續執行,當協程的任務完成時,再resume恢復切換到原來的線程上繼續執行。

在主線程進行的 suspendresume 的兩個操作, 既實現了將耗時任務交由后臺線程完成,保障了主線程安全 ,也在不增加代碼復雜度和保證代碼可讀性的前提下做到不阻塞主線程的執行。可以說,在 Android 平臺上協程主要就用來解決異步和切換線程這兩個問題。

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

    關注

    20

    文章

    2987

    瀏覽量

    107327
  • 編程
    +關注

    關注

    88

    文章

    3686

    瀏覽量

    94970
  • ui
    ui
    +關注

    關注

    0

    文章

    206

    瀏覽量

    21705
  • kotlin
    +關注

    關注

    0

    文章

    60

    瀏覽量

    4336
收藏 人收藏

    評論

    相關推薦
    熱點推薦

    談談的那些事兒

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

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

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

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

    及同步的開銷(3)方便切換控制流,簡化編程模型(4)高并發+高擴展性+低成本:一個CPU支持上萬的都不是問題。所以很適合用于高并發處理。
    的頭像 發表于 03-18 11:22 ?3902次閱讀

    關于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 ?1502次閱讀

    Python與JavaScript的對比及經驗技巧

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

    使用channel控制數量

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

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

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

    的概念及的掛起函數介紹

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

    Kotlin實戰進階1

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

    Kotlin實戰進階2

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

    FreeRTOS任務與介紹

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

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

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

    的實現與原理

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

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

    不是系統級線程,很多時候被稱為“輕量級線程”、“微線程”、“纖(fiber)”等。簡單來說可以認為
    的頭像 發表于 11-11 11:35 ?1586次閱讀
    Linux線程、線程與異步編程、<b class='flag-5'>協</b><b class='flag-5'>程</b>與異步介紹
    主站蜘蛛池模板: 四虎在线最新地址4hu | 国产亚洲午夜精品a一区二区 | 日本精品一卡二卡≡卡四卡 | 4480yy私人午夜a级国产 | 狂野欧美激情性xxxx | 毛片爱爱| 国产网站大全 | 人人玩人人添天天爽 | 综合伊人| 国产三级在线观看免费 | 狠狠躁夜夜躁人人躁婷婷视频 | 波多野结衣在线视频免费观看 | 免费观看视频高清www | 奇米影视一区 | 欧美精品人爱a欧美精品 | 一级片在线观看视频 | 美女免费视频是黄的 | 日本三级网站在线线观看 | 天天色天天干天天射 | 濑亚美莉iptd619在线观看 | 国产亚洲综合精品一区二区三区 | 黄 色 片免费观看 | 高h细节肉爽文男男 | cijilu刺激 国产免费的 | 亚洲成色在线综合网站 | 国产尤物在线视频 | 亚洲hh| 欧美视频一区在线观看 | 手机在线看片国产日韩生活片 | 天天干夜夜爽 | mide-776中文字幕在线 | 天天久久影视色香综合网 | 国产亚洲精品久久久久久久软件 | 欧美在线精品一区二区三区 | 五月婷婷六月丁香激情 | 一级毛片无毒不卡直接观看 | 看黄在线 | 天堂一区二区在线观看 | 免费观看黄a一级视频日本 免费观看黄色网页 | 极品啪啪 | 久热福利 |