You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Flow时出现“Suspension function can only be called...”错误排查

Flow中emit在回调内报错:Suspension functions can be called only within coroutine body

我正在学习响应式编程与Flow的使用,于是从联系人类中取出一个方法进行改写,尝试用Flow来发射数据。

代码如下:

val getMCxContactsFlow: Flow<MCxContact> = flow {
        val contact: MCxContact? = null
        val uri = RawContacts.CONTENT_URI
            .buildUpon()
            .appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName)
            .appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType)
            .build()

        val cursor = contentResolver.query(
            uri,
            arrayOf(Contacts._ID),
            null,
            null,
            null
        )
        getCursorInformation(cursor) { result ->
            result.apply {
                try {
                    val id = getStringOrNull(getColumnIndex(Contacts._ID))
                        ?: throw Exception("Cant find contact ID")
                    Log.d(TAG, id)
                    val query = contentResolver.query(
                        Data.CONTENT_URI,
                        null,
                        "${Data.RAW_CONTACT_ID} =?",
                        arrayOf(id),
                        null
                    )
                    val contacts = getContactFromQuery(query)
                    emit(contacts) // 触发错误的emit
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
        emit(contact!!) // 此处emit可正常工作
    }

    private fun getCursorInformation(
        cursor: Cursor?,
        iterator: (cursor: Cursor) -> Unit
    ) {
        if (cursor == null) throw Exception("getCursorInformation: cursor is null")
        else {
            cursor.apply {
                if (count > 0) {
                    while (moveToNext()) iterator(this)
                } else {
                    throw Exception("Cursor data is empty")
                }
                close()
            }
        }
    }

Android在标注的emit处报错:Suspension functions can be called only within coroutine body,而末尾的emit却能正常工作,猜测是作用域问题,但不清楚具体哪里出错了。


问题出在哪

问题核心是挂起函数的调用上下文限制:

  • flow{}代码块本身是协程体,属于挂起上下文,所以末尾的emit能正常调用。
  • 你传给getCursorInformation的iterator是普通的(Cursor) -> Unit函数,这个lambda的执行环境不在协程体内。而emit是挂起函数,只能在挂起函数或者协程体里调用,所以这里调用emit就会报错。

怎么改

把getCursorInformation改成挂起函数,同时把它的iterator参数也改成挂起函数类型,这样lambda里就能调用emit了:

修改后的代码:

val getMCxContactsFlow: Flow<MCxContact> = flow {
        val contact: MCxContact? = null
        val uri = RawContacts.CONTENT_URI
            .buildUpon()
            .appendQueryParameter(RawContacts.ACCOUNT_NAME, accountName)
            .appendQueryParameter(RawContacts.ACCOUNT_TYPE, accountType)
            .build()

        val cursor = contentResolver.query(
            uri,
            arrayOf(Contacts._ID),
            null,
            null,
            null
        )
        getCursorInformation(cursor) { result ->
            result.apply {
                try {
                    val id = getStringOrNull(getColumnIndex(Contacts._ID))
                        ?: throw Exception("Cant find contact ID")
                    Log.d(TAG, id)
                    val query = contentResolver.query(
                        Data.CONTENT_URI,
                        null,
                        "${Data.RAW_CONTACT_ID} =?",
                        arrayOf(id),
                        null
                    )
                    val contacts = getContactFromQuery(query)
                    emit(contacts) // 现在可以正常调用emit
                } catch (e: Exception) {
                    e.printStackTrace()
                }
            }
        }
        emit(contact!!)
    }

    // 修改为挂起函数,同时将iterator参数改为挂起函数类型
    private suspend fun getCursorInformation(
        cursor: Cursor?,
        iterator: suspend (cursor: Cursor) -> Unit
    ) {
        if (cursor == null) throw Exception("getCursorInformation: cursor is null")
        else {
            cursor.apply {
                if (count > 0) {
                    while (moveToNext()) iterator(this)
                } else {
                    throw Exception("Cursor data is empty")
                }
                close()
            }
        }
    }

简单说明

  • 给getCursorInformation加上suspend关键字,让它成为挂起函数,这样就能在flow{}的协程体内调用它。
  • 把iterator参数的类型改成suspend (Cursor) -> Unit,表示这个lambda是挂起函数,允许在内部调用emit这类挂起函数。

内容的提问来源于stack exchange,提问作者Chris Pi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 13:40:42