Kotlin Suspend掛起函數(shù)的使用詳解
總結(jié)
掛起(suspend)函數(shù)是所有協(xié)程的核心。 掛起函數(shù)可以執(zhí)行長時(shí)間運(yùn)行的操作并等待它完成而不會阻塞主線程。
掛起函數(shù)的語法與常規(guī)函數(shù)的語法類似,不同之處在于添加了suspend關(guān)鍵字。 它可以接受一個(gè)參數(shù)并有一個(gè)返回類型。 但是,掛起函數(shù)只能由另一個(gè)掛起函數(shù)或在協(xié)程內(nèi)調(diào)用。
suspend fun backgroundTask(param: Int): Int { // long running operation }
在背后,編譯器將掛起函數(shù)轉(zhuǎn)換為另一個(gè)沒有掛起關(guān)鍵字的函數(shù),該函數(shù)接受一個(gè)類型為 Continuation<T>
的附加參數(shù)。 例如,上面的函數(shù)將由編譯器轉(zhuǎn)換為:
fun backgroundTask(param: Int, callback: Continuation<Int>): Int { // long running operation }
本質(zhì)
- 掛起函數(shù)只能在協(xié)程或者其他掛起函數(shù)中調(diào)用。
- 掛起的對象是協(xié)程:launch ,async 或者其他函數(shù)創(chuàng)建的協(xié)程,在執(zhí)行到某一個(gè) suspend 函數(shù)的時(shí)候,這個(gè)協(xié)程會被掛起,即,從正在執(zhí)行它的線程上脫離。就是說,當(dāng)前線程跳過這個(gè)掛起函數(shù),繼續(xù)往下運(yùn)行,但另一方面,線程的代碼在到達(dá) suspend 函數(shù)的時(shí)候被掐斷,接下來協(xié)程會從這個(gè) suspend 函數(shù)開始繼續(xù)往下執(zhí)行,不過是在指定的線程,執(zhí)行完后,返回到之前掛起它的線程;
- 簡單來講,在 Kotlin 中所謂的掛起,就是一個(gè)稍后會被自動(dòng)切回來的線程調(diào)度操作;
- 掛起函數(shù)的特點(diǎn)是使用同步的方式完成異步任務(wù)。
withContext
的作用就是指定切換的線程,比如:suspend fun suspendingGetImage(id: String) = withContext(Dispatchers.IO)
。
何時(shí)使用
如果你的某個(gè)函數(shù)比較耗時(shí),也就是要等的操作,那就把它寫成 suspend 函數(shù)。這就是原則。
耗時(shí)操作一般分為兩類:I/O 操作和 CPU 計(jì)算工作。比如文件的讀寫、網(wǎng)絡(luò)交互、圖片的模糊處理,都是耗時(shí)的,通通可以把它們寫進(jìn) suspend 函數(shù)里。
另外這個(gè)「耗時(shí)」還有一種特殊情況,就是這件事本身做起來并不慢,但它需要等待,比如 5 秒鐘之后再做這個(gè)操作。這種也是 suspend 函數(shù)的應(yīng)用場景。
消除回調(diào)
假設(shè) postItem
由三個(gè)有依賴關(guān)系的異步子任務(wù)組成: requestToken
,createPost
和 processPost
,這三個(gè)函數(shù)都是基于回調(diào)的 API:
// 三個(gè)基于回調(diào)的 API fun requestToken(block: (String) -> Unit) fun createPost( token: String, item: Item, block: (Post) -> Unit) ) fun processPost(post: Post) fun postItem(item: Item) { requestToken { token -> createPost(token, item) { post -> processPost(post) } } }
可以看到基于回調(diào)的 API 很容易造成大量縮進(jìn)。如果代碼中再加上一些條件、循環(huán)的邏輯,那么代碼可讀性會大大降低。Kotlin 的 suspend 關(guān)鍵字可以幫助我們消除回調(diào),用同步的寫法寫異步:
suspend fun requestToken(): String suspend fun createPost(token: String, item: Item): Post suspend fun processPost(post) suspend fun postItem(item: Item) { val token = ?? requestToken() val post = ?? createPost(token, item) ?? processPost(post) }
由于 createPost 這些方法實(shí)際上是耗時(shí)的 IO 異步操作,需要等到拿到返回值才能執(zhí)行后面的邏輯,但我們又不希望阻塞當(dāng)前線程(通常是主線程),因此最終必須實(shí)現(xiàn)某種消息傳遞的機(jī)制,讓后臺線程做完耗時(shí)操作以后把結(jié)果傳給主線程。
一些例子
一個(gè)基本的使用方式:
suspend fun getUserInfo(): String { withContext(Dispatchers.IO) { delay(1000L) } return "BoyCoder" }
在 Room 里面會經(jīng)常用到:
@Dao interface RegisterDatabaseDao { @Insert suspend fun insert(register: RegisterEntity) //@Delete //suspend fun deleteSubscriber(register: RegisterEntity):Int @Query("SELECT * FROM Register_users_table ORDER BY userId DESC") fun getAllUsers(): LiveData<List<RegisterEntity>> @Query("DELETE FROM Register_users_table") suspend fun deleteAll(): Int @Query("SELECT * FROM Register_users_table WHERE user_name LIKE :userName") suspend fun getUsername(userName: String): RegisterEntity? }
最后這個(gè)例子可以直接在 Kotlin Playground 上跑。
import kotlinx.coroutines.* import java.util.* import java.time.LocalDate import java.time.format.DateTimeFormatter import java.time.Period import java.text.SimpleDateFormat import java.lang.Thread var dateTimeNow = "" @OptIn(DelicateCoroutinesApi::class) fun main() = runBlocking{ dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("code start: ${dateTimeNow}") launch { dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("1 code start: ${dateTimeNow}") delay(2000L) dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("2 Task from runBlocking: ${dateTimeNow}") } coroutineScope { // Creates a new coroutine scope dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("3 coroutineScope created: ${dateTimeNow}") val job = launch { dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("4 coroutineScope job starts: ${dateTimeNow}") val one = doSomethingUsefulOne() val two = doSomethingUsefulTwo() dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("5 coroutineScope job ends: ${dateTimeNow}") } val job2 = launch { dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("11 coroutineScope job2 starts: ${dateTimeNow}") } delay(1000L) dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("6 Task from first coroutine scope: ${dateTimeNow}") // Printed before initial launch //job.cancel() // This cancels nested launch's execution } dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("code end: ${dateTimeNow}") } fun dateAsString( dateInMillis: Long, format: String = "yyyyMMdd HH:mm:ss", locale: Locale = Locale.getDefault() ): String { val date = Date(dateInMillis) val formatter = SimpleDateFormat(format, locale) return formatter.format(date) } suspend fun doSomethingUsefulOne(): Int { dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("7 第一個(gè)掛起函數(shù)開始: ${dateTimeNow}") delay(1000L) // 假設(shè)我們在這里做了某些有用的工作 dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("8 第一個(gè)掛起函數(shù)結(jié)束: ${dateTimeNow}") return 1 } suspend fun doSomethingUsefulTwo(): Int { dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("9 第二個(gè)掛起函數(shù)開始: ${dateTimeNow}") delay(2000L) // 假設(shè)我們在這里也做了某些有用的工作 dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("10 第二個(gè)掛起函數(shù)結(jié)束: ${dateTimeNow}") coroutineScope { val job = launch { doSomethingUsefulThree() doSomethingUsefulFour() } } return 2 } suspend fun doSomethingUsefulThree(): Int { dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("9 第三個(gè)掛起函數(shù)開始: ${dateTimeNow}") delay(3000L) // 假設(shè)我們在這里也做了某些有用的工作 dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("10 第三個(gè)掛起函數(shù)結(jié)束: ${dateTimeNow}") return 3 } suspend fun doSomethingUsefulFour(): Int { dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("9 第四個(gè)掛起函數(shù)開始: ${dateTimeNow}") delay(3000L) // 假設(shè)我們在這里也做了某些有用的工作 dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("10 第四個(gè)掛起函數(shù)結(jié)束: ${dateTimeNow}") return 4 }
打印的結(jié)果如下:
code start: 20221009 03:15:55
3 coroutineScope created: 20221009 03:15:55
1 code start: 20221009 03:15:55
4 coroutineScope job starts: 20221009 03:15:55
11 coroutineScope job2 starts: 20221009 03:15:55
7 第一個(gè)掛起函數(shù)開始: 20221009 03:15:55
6 Task from first coroutine scope: 20221009 03:15:56
8 第一個(gè)掛起函數(shù)結(jié)束: 20221009 03:15:56
9 第二個(gè)掛起函數(shù)開始: 20221009 03:15:56
2 Task from runBlocking: 20221009 03:15:57
10 第二個(gè)掛起函數(shù)結(jié)束: 20221009 03:15:58
9 第三個(gè)掛起函數(shù)開始: 20221009 03:15:58
10 第三個(gè)掛起函數(shù)結(jié)束: 20221009 03:16:01
9 第四個(gè)掛起函數(shù)開始: 20221009 03:16:01
10 第四個(gè)掛起函數(shù)結(jié)束: 20221009 03:16:04
5 coroutineScope job ends: 20221009 03:16:04
code end: 20221009 03:16:04
有幾點(diǎn)需要說明:
- launch 是 CoroutineScope 的一個(gè)擴(kuò)展函數(shù),該方法在不阻塞當(dāng)前線程的情況下啟動(dòng)新的協(xié)程,launch 里面的代碼雖然有掛起函數(shù),但還是會按順序運(yùn)行(注意,這里的掛起函數(shù)并沒有用withContext選擇去指定切換的線程);
- coroutineScope 本身就是一個(gè)掛起函數(shù),會掛起當(dāng)前的協(xié)程。coroutineScope 里面的代碼除了 launch,其他按照順序運(yùn)行,而 coroutineScope 里面可以 launch 多個(gè) job,這多個(gè) job 是并行的;
- suspend 掛起函數(shù)里面的掛起函數(shù)是(默認(rèn))串行的(即,用同步的方式實(shí)現(xiàn)異步)。
到此這篇關(guān)于Kotlin Suspend掛起函數(shù)的使用詳解的文章就介紹到這了,更多相關(guān)Kotlin Suspend掛起函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android自定義popupwindow實(shí)例代碼
這篇文章主要為大家詳細(xì)介紹了Android自定義popupwindow實(shí)例代碼,popupwindow彈出菜單效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-11-11Android在Sqlite3中的應(yīng)用及多線程使用數(shù)據(jù)庫的建議(實(shí)例代碼)
這篇文章主要介紹了Android在Sqlite3中的應(yīng)用及多線程使用數(shù)據(jù)庫的建議,包括編寫數(shù)據(jù)庫具體操作類、增刪改查,通過實(shí)例代碼介紹了在實(shí)際中的應(yīng)用,需要的朋友可以參考下2022-04-04Flutter使用sqflite處理數(shù)據(jù)表變更的方法詳解
了解過數(shù)據(jù)庫的同學(xué)應(yīng)該會知道,數(shù)據(jù)表結(jié)構(gòu)是可能發(fā)生改變的。所以本文為大家介紹了Flutter?使用?sqflite?處理數(shù)據(jù)表變更的版本升級處理方法,感興趣的可以了解一下2023-04-04android 跳轉(zhuǎn)到應(yīng)用通知設(shè)置界面的示例
本篇文章主要介紹了android 跳轉(zhuǎn)到應(yīng)用通知設(shè)置界面的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-10-10Android網(wǎng)頁H5 Input選擇相機(jī)和系統(tǒng)相冊
這篇文章主要為大家詳細(xì)介紹了Android網(wǎng)頁H5 Input選擇相機(jī)和系統(tǒng)相冊,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-10-10如何使用Flutter實(shí)現(xiàn)手寫簽名效果
Flutter插件提供了用于繪制平滑簽名的簽名板,下面這篇文章主要給大家介紹了關(guān)于如何使用Flutter實(shí)現(xiàn)手寫簽名效果的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12