挂起(suspend)函数是所有协程的核心。 挂起函数可以执行长时间运行的操作并等待它完成而不会阻塞主线程。
挂起函数的语法与常规函数的语法类似,不同之处在于添加了suspend关键字。 它可以接受一个参数并有一个返回类型。 但是,挂起函数只能由另一个挂起函数或在协程内调用。
suspend fun backgroundTask(param: Int): Int { // long running operation }
在背后,编译器将挂起函数转换为另一个没有挂起关键字的函数,该函数接受一个类型为 Continuation<T>
的附加参数。 例如,上面的函数将由编译器转换为:
fun backgroundTask(param: Int, callback: Continuation<Int>): Int { // long running operation }
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 =