網站首頁 編程語言 正文
總結
掛起(suspend)函數是所有協程的核心。 掛起函數可以執行長時間運行的操作并等待它完成而不會阻塞主線程。
掛起函數的語法與常規函數的語法類似,不同之處在于添加了suspend關鍵字。 它可以接受一個參數并有一個返回類型。 但是,掛起函數只能由另一個掛起函數或在協程內調用。
suspend fun backgroundTask(param: Int): Int { // long running operation }
在背后,編譯器將掛起函數轉換為另一個沒有掛起關鍵字的函數,該函數接受一個類型為 Continuation<T>
的附加參數。 例如,上面的函數將由編譯器轉換為:
fun backgroundTask(param: Int, callback: Continuation<Int>): Int { // long running operation }
本質
- 掛起函數只能在協程或者其他掛起函數中調用。
- 掛起的對象是協程:launch ,async 或者其他函數創建的協程,在執行到某一個 suspend 函數的時候,這個協程會被掛起,即,從正在執行它的線程上脫離。就是說,當前線程跳過這個掛起函數,繼續往下運行,但另一方面,線程的代碼在到達 suspend 函數的時候被掐斷,接下來協程會從這個 suspend 函數開始繼續往下執行,不過是在指定的線程,執行完后,返回到之前掛起它的線程;
- 簡單來講,在 Kotlin 中所謂的掛起,就是一個稍后會被自動切回來的線程調度操作;
- 掛起函數的特點是使用同步的方式完成異步任務。
-
withContext
的作用就是指定切換的線程,比如:suspend fun suspendingGetImage(id: String) = withContext(Dispatchers.IO)
。
何時使用
如果你的某個函數比較耗時,也就是要等的操作,那就把它寫成 suspend 函數。這就是原則。
耗時操作一般分為兩類:I/O 操作和 CPU 計算工作。比如文件的讀寫、網絡交互、圖片的模糊處理,都是耗時的,通通可以把它們寫進 suspend 函數里。
另外這個「耗時」還有一種特殊情況,就是這件事本身做起來并不慢,但它需要等待,比如 5 秒鐘之后再做這個操作。這種也是 suspend 函數的應用場景。
消除回調
假設 postItem
由三個有依賴關系的異步子任務組成: requestToken
,createPost
和 processPost
,這三個函數都是基于回調的 API:
// 三個基于回調的 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) } } }
可以看到基于回調的 API 很容易造成大量縮進。如果代碼中再加上一些條件、循環的邏輯,那么代碼可讀性會大大降低。Kotlin 的 suspend 關鍵字可以幫助我們消除回調,用同步的寫法寫異步:
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 這些方法實際上是耗時的 IO 異步操作,需要等到拿到返回值才能執行后面的邏輯,但我們又不希望阻塞當前線程(通常是主線程),因此最終必須實現某種消息傳遞的機制,讓后臺線程做完耗時操作以后把結果傳給主線程。
一些例子
一個基本的使用方式:
suspend fun getUserInfo(): String { withContext(Dispatchers.IO) { delay(1000L) } return "BoyCoder" }
在 Room 里面會經常用到:
@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? }
最后這個例子可以直接在 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 第一個掛起函數開始: ${dateTimeNow}") delay(1000L) // 假設我們在這里做了某些有用的工作 dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("8 第一個掛起函數結束: ${dateTimeNow}") return 1 } suspend fun doSomethingUsefulTwo(): Int { dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("9 第二個掛起函數開始: ${dateTimeNow}") delay(2000L) // 假設我們在這里也做了某些有用的工作 dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("10 第二個掛起函數結束: ${dateTimeNow}") coroutineScope { val job = launch { doSomethingUsefulThree() doSomethingUsefulFour() } } return 2 } suspend fun doSomethingUsefulThree(): Int { dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("9 第三個掛起函數開始: ${dateTimeNow}") delay(3000L) // 假設我們在這里也做了某些有用的工作 dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("10 第三個掛起函數結束: ${dateTimeNow}") return 3 } suspend fun doSomethingUsefulFour(): Int { dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("9 第四個掛起函數開始: ${dateTimeNow}") delay(3000L) // 假設我們在這里也做了某些有用的工作 dateTimeNow = dateAsString(Calendar.getInstance().time.time) println("10 第四個掛起函數結束: ${dateTimeNow}") return 4 }
打印的結果如下:
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 第一個掛起函數開始: 20221009 03:15:55
6 Task from first coroutine scope: 20221009 03:15:56
8 第一個掛起函數結束: 20221009 03:15:56
9 第二個掛起函數開始: 20221009 03:15:56
2 Task from runBlocking: 20221009 03:15:57
10 第二個掛起函數結束: 20221009 03:15:58
9 第三個掛起函數開始: 20221009 03:15:58
10 第三個掛起函數結束: 20221009 03:16:01
9 第四個掛起函數開始: 20221009 03:16:01
10 第四個掛起函數結束: 20221009 03:16:04
5 coroutineScope job ends: 20221009 03:16:04
code end: 20221009 03:16:04
有幾點需要說明:
- launch 是 CoroutineScope 的一個擴展函數,該方法在不阻塞當前線程的情況下啟動新的協程,launch 里面的代碼雖然有掛起函數,但還是會按順序運行(注意,這里的掛起函數并沒有用withContext選擇去指定切換的線程);
- coroutineScope 本身就是一個掛起函數,會掛起當前的協程。coroutineScope 里面的代碼除了 launch,其他按照順序運行,而 coroutineScope 里面可以 launch 多個 job,這多個 job 是并行的;
- suspend 掛起函數里面的掛起函數是(默認)串行的(即,用同步的方式實現異步)。
原文鏈接:https://blog.csdn.net/zyctimes/article/details/127140202
相關推薦
- 2023-01-17 C#實現帶行數和標尺的RichTextBox_C#教程
- 2022-12-14 python矩陣的基本運算及各種操作_python
- 2023-02-15 react源碼合成事件深入解析_React
- 2023-07-18 SpringBoot Cache 整合 Redis 緩存框架
- 2024-07-15 SpringBoot使用itext導出pdf(含圖片和表格)
- 2023-01-28 Flask框架運用Axios庫實現前后端交互詳解_python
- 2022-08-02 Android開發自定義雙向SeekBar拖動條控件_Android
- 2022-07-14 android實現多點觸摸效果_Android
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支