首页 > 解决方案 > 递归调用 Kotlin 挂起函数是否安全?

问题描述

我有一个 API 可能会响应“稍后再试”。在这种情况下,我想递归调用该函数(以自动传播取消)。

简化示例:

suspend fun loadData() {
    runCatching { someApi.loadData() }
        .onSuccess { response ->
            if (response is Response.TryAgain) {
                delay(1000)
                loadData()
            }
        }
}

如果我理解正确,则此代码不应导致StackOverflowError(与常规的非挂起递归函数相反)。

我从Roman Elizarov 的文章中得到了这个想法,但我在这里没有使用 a DeepRecursiveFunction

我为Android编写了一些测试,似乎:

  1. suspend没有悬挂点的函数在 1000-4000 左右的深度抛出StackOverflowError(就像非悬挂点一样)
  2. suspend带有悬挂点(yield()delay())的函数允许达到 1-2 百万的深度,其中OutOfMemoryError发生

这种行为是否记录在某个地方,或者它只是一个将来可能会改变的实现细节?我在coroutines design documentcoroutines guide以及这里的 SO中都没有找到答案。

总的来说,如果调用深度远低于 100 万,使用这种模式是否是个好主意?有更好的可取消解决方案吗?

标签: kotlinrecursionkotlin-coroutinessuspend

解决方案


如果tailrec优化不适用于您的情况,您可以手动将递归函数转换为 while-loop:

suspend fun loadData() {
    var getResponse = false
    while (!getResponse) {
        runCatching { someApi.loadData() }
            .onSuccess { response ->
                if (response is Response.TryAgain) {
                    delay(1000)
                } else {
                    getResponse = true
                }
            }
    }    
}

推荐阅读