自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Android進階之Kotin協(xié)程原理和啟動方式詳細講解(優(yōu)雅使用協(xié)程)

開發(fā) 前端
協(xié)程就是方法調(diào)用封裝成類線程的API。方法調(diào)用當然比線程切換輕量;而封裝成類線程的API后,它形似線程(可手動啟動、有各種運行狀態(tài)、能夠協(xié)作工作、能夠并發(fā)執(zhí)行)。

[[415384]]

前言

kotlin的協(xié)程在初學者看來是一個很神奇的東西,居然能做到用同步的代碼塊實現(xiàn)異步的調(diào)用,其實深入了解你會發(fā)現(xiàn)kotlin協(xié)程本質(zhì)上是通過函數(shù)式編程的風格對Java線程池的一種封裝,這樣會帶來很多好處,首先是函數(shù)式+響應式編程風格避免了回調(diào)地獄,這也可以說是實現(xiàn)promise,future等語言(比如js)的進一步演進。其次是能夠避免開發(fā)者的失誤導致的線程切換過多的性能損失。

那么我們就來看看協(xié)程

一、協(xié)程(Coroutines)是什么

1、協(xié)程是輕量級線程

  • 協(xié)程是一種并發(fā)設計模式,您可以在 Android 平臺上使用它來簡化異步執(zhí)行的代碼。
  • 協(xié)程就是方法調(diào)用封裝成類線程的API。方法調(diào)用當然比線程切換輕量;而封裝成類線程的API后,它形似線程(可手動啟動、有各種運行狀態(tài)、能夠協(xié)作工作、能夠并發(fā)執(zhí)行)。因此從這個角度說,它是輕量級線程沒錯;
  • 當然,協(xié)程絕不僅僅是方法調(diào)用,因為方法調(diào)用不能在一個方法執(zhí)行到一半時掛起,之后又在原點恢復。這一點可以使用EventLoop之類的方式實現(xiàn)。想象一下在庫級別將回調(diào)風格或Promise/Future風格的異步代碼封裝成同步風格,封裝的結(jié)果就非常接近協(xié)程;

2、線程運行在內(nèi)核態(tài),協(xié)程運行在用戶態(tài)

主要明白什么叫用戶態(tài),我們寫的幾乎所有代碼,都執(zhí)行在用戶態(tài),協(xié)程對于操作系統(tǒng)來說僅僅是第三方提供的庫而已,當然運行在用戶態(tài)。而線程是操作系統(tǒng)級別的東西,運行在內(nèi)核態(tài)。

3、協(xié)程是一個線程框架

Kotlin的協(xié)程庫可以指定協(xié)程運行的線程池,我們只需要操作協(xié)程,必要的線程切換操作交給庫,從這個角度來說,協(xié)程就是一個線程框架

4、協(xié)程實現(xiàn)

協(xié)程,顧名思義,就是相互協(xié)作的子程序,多個子程序之間通過一定的機制相互關聯(lián)、協(xié)作地完成某項任務。比如一個協(xié)程在執(zhí)行上可以被分為多個子程序,每個子程序執(zhí)行完成后主動掛起,等待合適的時機再恢復;一個協(xié)程被掛起時,線程可以執(zhí)行其它子程序,從而達到線程高利用率的多任務處理目的——協(xié)程在一個線程上執(zhí)行多個任務,而傳統(tǒng)線程只能執(zhí)行一個任務,從多任務執(zhí)行的角度,協(xié)程自然比線程輕量;

5、協(xié)程解決的問題

同步的方式寫異步代碼。如果不使用協(xié)程,我們目前能夠使用的API形式主要有三種:純回調(diào)風格(如AIO)、RxJava、Promise/Future風格,他們普遍存在回調(diào)地獄問題,解回調(diào)地獄只能通過行數(shù)換層數(shù),且對于不熟悉異步風格的程序員來說,能夠看懂較為復雜的異步代碼就比較費勁。

6、協(xié)程優(yōu)點

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

二、協(xié)程使用

  1. 依賴 
  2. dependencies { 
  3.     implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' 

協(xié)程需要運行在協(xié)程上下文環(huán)境,在非協(xié)程環(huán)境中憑空啟動協(xié)程,有三種方式

1、runBlocking{}

啟動一個新協(xié)程,并阻塞當前線程,直到其內(nèi)部所有邏輯及子協(xié)程邏輯全部執(zhí)行完成。

該方法的設計目的是讓suspend風格編寫的庫能夠在常規(guī)阻塞代碼中使用,常在main方法和測試中使用。

  1. override fun onCreate(savedInstanceState: Bundle?) { 
  2.     super.onCreate(savedInstanceState) 
  3.     setContentView(R.layout.activity_main) 
  4.     Log.e(TAG, "主線程id:${mainLooper.thread.id}"
  5.     test() 
  6.     Log.e(TAG, "協(xié)程執(zhí)行結(jié)束"
  7. private fun test() = runBlocking { 
  8.     repeat(8) { 
  9.         Log.e(TAG, "協(xié)程執(zhí)行$it 線程id:${Thread.currentThread().id}"
  10.         delay(1000) 
  11.     } 
圖片

runBlocking啟動的協(xié)程任務會阻斷當前線程,直到該協(xié)程執(zhí)行結(jié)束。當協(xié)程執(zhí)行結(jié)束之后,頁面才會被顯示出來。

2、GlobalScope.launch{}

在應用范圍內(nèi)啟動一個新協(xié)程,協(xié)程的生命周期與應用程序一致。這樣啟動的協(xié)程并不能使線程?;?,就像守護線程。

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

  1. override fun onCreate(savedInstanceState: Bundle?) { 
  2.     super.onCreate(savedInstanceState) 
  3.     setContentView(R.layout.activity_main) 
  4.     Log.e(TAG, "主線程id:${mainLooper.thread.id}"
  5.     val job = GlobalScope.launch { 
  6.         delay(6000) 
  7.         Log.e(TAG, "協(xié)程執(zhí)行結(jié)束 -- 線程id:${Thread.currentThread().id}"
  8.     } 
  9.     Log.e(TAG, "主線程執(zhí)行結(jié)束"

 

  1. //Job中的方法 
  2. job.isActive 
  3. job.isCancelled 
  4. job.isCompleted 
  5. job.cancel() 
  6. jon.join() 

從執(zhí)行結(jié)果看出,launch不會阻斷主線程。

下面我們來總結(jié)launch

我們看一下launch方法的定義:

  1. public fun CoroutineScope.launch( 
  2.     context: CoroutineContext = EmptyCoroutineContext, 
  3.     start: CoroutineStart = CoroutineStart.DEFAULT
  4.     block: suspend CoroutineScope.() -> Unit 
  5. ): Job { 
  6.     val newContext = newCoroutineContext(context) 
  7.     val coroutine = if (start.isLazy) 
  8.         LazyStandaloneCoroutine(newContext, block) else 
  9.         StandaloneCoroutine(newContext, active = true
  10.     coroutine.start(start, coroutine, block) 
  11.     return coroutine 

從方法定義中可以看出,launch() 是CoroutineScope的一個擴展函數(shù),CoroutineScope簡單來說就是協(xié)程的作用范圍。launch方法有三個參數(shù):1.協(xié)程下上文;2.協(xié)程啟動模式;3.協(xié)程體:block是一個帶接收者的函數(shù)字面量,接收者是CoroutineScope

①.協(xié)程下上文

  • 上下文可以有很多作用,包括攜帶參數(shù),攔截協(xié)程執(zhí)行等等,多數(shù)情況下我們不需要自己去實現(xiàn)上下文,只需要使用現(xiàn)成的就好。上下文有一個重要的作用就是線程切換,Kotlin協(xié)程使用調(diào)度器來確定哪些線程用于協(xié)程執(zhí)行,Kotlin提供了調(diào)度器給我們使用:
  • Dispatchers.Main:使用這個調(diào)度器在 Android 主線程上運行一個協(xié)程??梢杂脕砀耈I 。在UI線程中執(zhí)行
  • Dispatchers.IO:這個調(diào)度器被優(yōu)化在主線程之外執(zhí)行磁盤或網(wǎng)絡 I/O。在線程池中執(zhí)行
  • Dispatchers.Default:這個調(diào)度器經(jīng)過優(yōu)化,可以在主線程之外執(zhí)行 cpu 密集型的工作。例如對列表進行排序和解析 JSON。在線程池中執(zhí)行。
  • Dispatchers.Unconfined:在調(diào)用的線程直接執(zhí)行。
  • 調(diào)度器實現(xiàn)了CoroutineContext接口。

②.啟動模式

在Kotlin協(xié)程當中,啟動模式定義在一個枚舉類中:

  1. public enum class CoroutineStart { 
  2.     DEFAULT
  3.     LAZY, 
  4.     @ExperimentalCoroutinesApi 
  5.     ATOMIC, 
  6.     @ExperimentalCoroutinesApi 
  7.     UNDISPATCHED; 

一共定義了4種啟動模式,下表是含義介紹:

  • DEFAULT:默認的模式,立即執(zhí)行協(xié)程體
  • LAZY:只有在需要的情況下運行
  • ATOMIC:立即執(zhí)行協(xié)程體,但在開始運行之前無法取消
  • UNDISPATCHED:立即在當前線程執(zhí)行協(xié)程體,直到第一個 suspend 調(diào)用

③.協(xié)程體

協(xié)程體是一個用suspend關鍵字修飾的一個無參,無返回值的函數(shù)類型。被suspend修飾的函數(shù)稱為掛起函數(shù),與之對應的是關鍵字resume(恢復),注意:掛起函數(shù)只能在協(xié)程中和其他掛起函數(shù)中調(diào)用,不能在其他地方使用。

suspend函數(shù)會將整個協(xié)程掛起,而不僅僅是這個suspend函數(shù),也就是說一個協(xié)程中有多個掛起函數(shù)時,它們是順序執(zhí)行的??聪旅娴拇a示例:

  1. override fun onCreate(savedInstanceState: Bundle?) { 
  2.     super.onCreate(savedInstanceState) 
  3.     setContentView(R.layout.activity_main) 
  4.     GlobalScope.launch { 
  5.         val token = getToken() 
  6.         val userInfo = getUserInfo(token) 
  7.         setUserInfo(userInfo) 
  8.     } 
  9.     repeat(8){ 
  10.         Log.e(TAG,"主線程執(zhí)行$it"
  11.     } 
  12. private fun setUserInfo(userInfo: String) { 
  13.     Log.e(TAG, userInfo) 
  14. private suspend fun getToken(): String { 
  15.     delay(2000) 
  16.     return "token" 
  17. private suspend fun getUserInfo(token: String): String { 
  18.     delay(2000) 
  19.     return "$token - userInfo" 

getToken方法將協(xié)程掛起,協(xié)程中其后面的代碼永遠不會執(zhí)行,只有等到getToken掛起結(jié)束恢復后才會執(zhí)行。同時協(xié)程掛起后不會阻塞其他線程的執(zhí)行。

3.async/await:Deferred

async跟launch的用法基本一樣,區(qū)別在于:async的返回值是Deferred,將最后一個封裝成了該對象。async可以支持并發(fā),此時一般都跟await一起使用,看下面的例子。

async和await是兩個函數(shù),這兩個函數(shù)在我們使用過程中一般都是成對出現(xiàn)的;

async用于啟動一個異步的協(xié)程任務,await用于去得到協(xié)程任務結(jié)束時返回的結(jié)果,結(jié)果是通過一個Deferred對象返回的

  1. override fun onCreate(savedInstanceState: Bundle?) { 
  2.     super.onCreate(savedInstanceState) 
  3.     setContentView(R.layout.activity_main) 
  4.     GlobalScope.launch { 
  5.         val result1 = GlobalScope.async { 
  6.             getResult1() 
  7.         } 
  8.         val result2 = GlobalScope.async { 
  9.             getResult2() 
  10.         } 
  11.         val result = result1.await() + result2.await() 
  12.         Log.e(TAG,"result = $result"
  13.     } 
  14. private suspend fun getResult1(): Int { 
  15.     delay(3000) 
  16.     return 1 
  17. private suspend fun getResult2(): Int { 
  18.     delay(4000) 
  19.     return 2 

async是不阻塞線程的,也就是說getResult1和getResult2是同時進行的,所以獲取到result的時間是4s,而不是7s。

三、協(xié)程異常

1、因協(xié)程取消,協(xié)程內(nèi)部suspend方法拋出的CancellationException

2、常規(guī)異常,這類異常,有兩種異常傳播機制

  • launch:將異常自動向父協(xié)程拋出,將會導致父協(xié)程退出
  • async: 將異常暴露給用戶(通過捕獲deffer.await()拋出的異常)

例子講解

  1. fun main() = runBlocking { 
  2.     val job = GlobalScope.launch { // root coroutine with launch 
  3.         println("Throwing exception from launch"
  4.         throw IndexOutOfBoundsException() // 我們將在控制臺打印 Thread.defaultUncaughtExceptionHandler 
  5.     } 
  6.     job.join() 
  7.     println("Joined failed job"
  8.     val deferred = GlobalScope.async { // root coroutine with async 
  9.         println("Throwing exception from async"
  10.         throw ArithmeticException() // 沒有打印任何東西,依賴用戶去調(diào)用等待 
  11.     } 
  12.     try { 
  13.         deferred.await() 
  14.         println("Unreached"
  15.     } catch (e: ArithmeticException) { 
  16.         println("Caught ArithmeticException"
  17.     } 

結(jié)果

  1. Throwing exception from launch 
  2. Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.IndexOutOfBoundsException 
  3. Joined failed job 
  4. Throwing exception from async 
  5. Caught ArithmeticException 

總結(jié):

  • 協(xié)程是可以被取消的和超時控制,可以組合被掛起的函數(shù),協(xié)程中運行環(huán)境的指定,也就是線程的切換
  • 協(xié)程最常用的功能是并發(fā),而并發(fā)的典型場景就是多線程。
  • 協(xié)程設計的初衷是為了解決并發(fā)問題,讓協(xié)作式多任務實現(xiàn)起來更加方便。
  • 簡單理解 Kotlin 協(xié)程的話,就是封裝好的線程池,也可以理解成一個線程框架。

本文轉(zhuǎn)載自微信公眾號「 Android開發(fā)編程」,可以通過以下二維碼關注。轉(zhuǎn)載本文請聯(lián)系 Android開發(fā)編程眾號。

 

責任編輯:姜華 來源: Android開發(fā)編程
相關推薦

2021-05-20 09:14:09

Kotlin協(xié)程掛起和恢復

2021-09-16 09:59:13

PythonJavaScript代碼

2024-02-05 09:06:25

Python協(xié)程Asyncio庫

2023-10-24 19:37:34

協(xié)程Java

2023-08-08 07:18:17

協(xié)程管道函數(shù)

2023-11-17 11:36:59

協(xié)程纖程操作系統(tǒng)

2021-04-25 09:36:20

Go協(xié)程線程

2025-02-08 09:13:40

2021-12-09 06:41:56

Python協(xié)程多并發(fā)

2020-07-07 09:19:28

Android 協(xié)程開發(fā)

2020-04-08 09:06:34

Android 協(xié)程開發(fā)

2022-09-12 06:35:00

C++協(xié)程協(xié)程狀態(tài)

2019-10-23 14:34:15

KotlinAndroid協(xié)程

2024-05-29 08:05:15

Go協(xié)程通信

2023-12-27 08:07:49

Golang協(xié)程池Ants

2023-12-05 13:46:09

解密協(xié)程線程隊列

2016-10-28 17:39:47

phpgolangcoroutine

2017-05-02 11:38:00

PHP協(xié)程實現(xiàn)過程

2020-11-29 17:03:08

進程線程協(xié)程

2023-11-04 20:00:02

C++20協(xié)程
點贊
收藏

51CTO技術(shù)棧公眾號