【问题标题】:Is it safe to call Kotlin suspend function recursively?递归调用 Kotlin 挂起函数是否安全?
【发布时间】:2020-11-03 12:21:28
【问题描述】:

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

简化示例:

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

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

我从the article by Roman Elizarov 得到这个想法,但我没有在这里使用DeepRecursiveFunction。

我为 Android 写了some tests,似乎:

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

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

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

【问题讨论】:

  • 在这个简化的例子中,tailrec 修饰符看起来是一个完美的选择。它适合您的实际情况吗?
  • 感谢您的建议!不幸的是,实际用例包括try/catch(或runCatching),tailrec 不支持。我编辑了问题以反映这一点。

标签: kotlin recursion kotlin-coroutines suspend


【解决方案1】:

如果 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
                }
            }
    }    
}

【讨论】:

  • 是的,我目前有这样的东西。但是递归解决方案更短并且(对我而言)逻辑上也更清晰,所以我宁愿选择它。你能补充一些喜欢这个的理由吗?
  • 无论重试次数如何,它都能将您从StackOverflowError 中拯救出来,但是,是的,它看起来不如递归的好。您可以将这种方法与Template method pattern 混合使用,这样它就可以重复使用,而所有“坏部分”都将被编写一次并隐藏在模板中。
  • 好吧,“错误重试”可以用runRetrying 之类的东西很好地抽象出来。在这里它可能需要〜4 lambdas(操作本身,“重试”检查器,有效响应处理,错误处理),这看起来......有点奇怪。 :) 我仍然希望有人肯定地说:递归解决方案是否安全。
猜你喜欢
  • 2018-06-16
  • 2015-07-04
  • 2021-09-14
  • 1970-01-01
  • 2018-02-24
  • 1970-01-01
  • 2020-10-11
  • 2021-01-20
  • 2016-06-13
相关资源
最近更新 更多