【问题标题】:When should I make my normal function suspending function?我什么时候应该让我的正常功能暂停功能?
【发布时间】:2018-11-09 14:49:03
【问题描述】:

使用最新稳定版本的 kotlin 协程,我试图使用它来实现我的应用程序的一项功能。但是,我有点困惑。

基本上,我有一个函数可以在项目列表上做一些工作。这需要大约 700-1000 毫秒。

fun processList(list : ArrayList<String>) : ArrayList<Info> {

    val result = mutableListOf<Info>()

    for (item in list) {
        // Process list item
        // Each list item takes about ~ 15-20ms
        result.add(info) // add processed info to result
    }

    return result  // return result
}

现在,我希望它不阻塞主线程。所以我在启动块中启动这个函数,这样它就不会阻塞主线程。

coroutineScope.launch(Dispatchers.Main) {
    val result = processList(list)
}

这很好用。

但是,我尝试将该函数设置为挂起函数,以确保它永远不会阻塞主线程。实际上,函数内部没有启动任何其他协程。还尝试在单独的协程中处理每个列表项,然后将它们全部加入以使其实际使用子协程。但是循环内的那个块使用同步方法调用。所以没有必要让它异步 - 并行。所以我最终有一个这样的挂起函数:

suspend fun processList(list : ArrayList<String>) : ArrayList<Info> = coroutineScope {

    val result = mutableListOf<Info>()

    for (item in list) {
        // Process list item
        // Each list item takes about ~ 15-20ms
        result.add(info) // add processed info to result
    }

    return result  // return result
}

开头只有一个挂起修饰符,方法块用coroutineScope { }包裹。

这还重要吗?哪一个更好?如果它使用协程或长时间运行的函数也应该标记为挂起函数,我应该只制作函数挂起函数吗?

我很困惑。我看过最近关于协程的所有演讲,但不清楚这一点。

谁能帮我理解这个?

更新:

所以我最终拥有了这样的功能。只是为了确保永远不会在主线程上调用该函数。并且调用函数不必在任何地方都记住需要在后台线程上调用它。通过这种方式,我可以将调用函数的东西抽象化:只要按照指示去做,我不在乎你想在哪里处理这些东西。只需处理并给我结果。所以它自己负责它需要运行的位置,而不是调用函数。

suspend fun processList(list : ArrayList<String>) : ArrayList<Info> = coroutineScope {

    val result = mutableListOf<Info>()

    launch(Dispatchers.Default) {
        for (item in list) {
            // Process list item
            // Each list item takes about ~ 15-20ms
            result.add(info) // add processed info to result
        }
    }.join() // wait for the task to complete on the background thread

    return result  // return result
}

这是正确的方法吗?

【问题讨论】:

    标签: kotlin kotlin-coroutines


    【解决方案1】:

    您希望将 CPU 密集型计算卸载到后台线程,这样您的 GUI 线程就不会被阻塞。您不必声明任何挂起功能来实现这一点。这就是你需要的:

    myActivity.launch {
        val processedList = withContext(Default) { processList(list) }
        ... use processedList, you're on the GUI thread here ...
    }
    

    以上假设您已将CoroutineScope 接口正确添加到您的活动中,如其documentation 中所述。

    更好的做法是将withContext 推入processList 的定义中,这样你就不会在主线程上运行它了。声明如下:

    suspend fun processList(list: List<String>): List<Info> = withContext(Default) {
        list.map { it.toInfo() }
    }
    

    这假设您已将字符串到信息的逻辑放入

    fun String.toInfo(): Info = // whatever it takes
    

    【讨论】:

    • 问题是,processList() 函数是一个 cpu 重的函数。所以,确保它永远不会在主线程上被调用,更好。不是吗?
    • 我写的代码和你描述的完全一样:在后台线程中运行processList
    • 是的,它完全在后台线程中执行。但是调用函数需要处理那件事。我已经更新了这个问题。请检查我正在谈论的示例
    【解决方案2】:

    挂起的函数是回调的糖。它允许您以线性方式编写带有回调的代码。如果你的函数内部没有回调调用,也没有调用另一个挂起的函数,那么我认为让你的函数挂起是没有意义的。除非您想在后台线程中卸载函数内部的工作(挂起的函数并不总是与后台线程有关)-在这种情况下,您可以将launch/async 与适当的调度程序一起使用。在这种情况下,您可以选择将您的函数包装在launch/async 中,或者让您的函数暂停并在其中使用launch/async

    【讨论】:

    • 我已经更新了这个问题。我只是这样做了,发现它好一点。你能检查一下吗?
    • 对我来说看起来不错,除了对于异步计算,最好始终绑定到范围(如活动),以便在范围终止时可以取消计算。在您的示例中,这意味着在每次循环迭代时检查 isActive
    • 是的,所有的协程都绑定在activity范围内。因此,一旦活动/片段被销毁,所有子协程都会递归取消。
    猜你喜欢
    • 1970-01-01
    • 2010-09-17
    • 1970-01-01
    • 2018-11-19
    • 1970-01-01
    • 2021-02-01
    • 1970-01-01
    • 2017-01-08
    • 2011-06-12
    相关资源
    最近更新 更多