文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

KotlinSuspend挂起函数的使用详解

2023-02-17 15:00

关注

总结

挂起(suspend)函数是所有协程的核心。 挂起函数可以执行长时间运行的操作并等待它完成而不会阻塞主线程。

挂起函数的语法与常规函数的语法类似,不同之处在于添加了suspend关键字。 它可以接受一个参数并有一个返回类型。 但是,挂起函数只能由另一个挂起函数或在协程内调用。

suspend fun backgroundTask(param: Int): Int {
     // long running operation
}

在背后,编译器将挂起函数转换为另一个没有挂起关键字的函数,该函数接受一个类型为 Continuation<T> 的附加参数。 例如,上面的函数将由编译器转换为:

fun backgroundTask(param: Int, callback: Continuation<Int>): Int {
   // long running operation
}

本质

何时使用

如果你的某个函数比较耗时,也就是要等的操作,那就把它写成 suspend 函数。这就是原则。

耗时操作一般分为两类:I/O 操作和 CPU 计算工作。比如文件的读写、网络交互、图片的模糊处理,都是耗时的,通通可以把它们写进 suspend 函数里。

另外这个「耗时」还有一种特殊情况,就是这件事本身做起来并不慢,但它需要等待,比如 5 秒钟之后再做这个操作。这种也是 suspend 函数的应用场景。

消除回调

假设 postItem 由三个有依赖关系的异步子任务组成: requestTokencreatePostprocessPost,这三个函数都是基于回调的 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

有几点需要说明:

到此这篇关于Kotlin Suspend挂起函数的使用详解的文章就介绍到这了,更多相关Kotlin Suspend挂起函数内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯