首页 > 解决方案 > AndroidViewModel 按需取消作业

问题描述

我在AndroidViewModel班里有一份工作。作业由 触发viewModelScope.launch。Job 是一个长时间运行的进程,它通过 lambda 函数返回结果。根据要求如果用户想取消作业,同时保持在按钮的范围内单击它应该取消作业。问题是当我取消作业时,进程仍在后台运行,并且正在计算后台任务。下面是我的 ViewModelClass 及其作业和取消功能。

import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.*
class SelectionViewModel(val app: Application) : AndroidViewModel(app) {

private var mainJob: Job? = null
private var context: Context? = null

fun performAction(
    fileOptions: FileOptions,
    onSuccess: (ArrayList<String>?) -> Unit,
    onFailure: (String?) -> Unit,
    onProgress: (Pair<Int, Int>) -> Unit
) {
    mainJob = viewModelScope.launch {
        withContext(Dispatchers.IO) {
            kotlin.runCatching {

                while (isActive) {
                    val mOutputFilePaths: ArrayList<String> = ArrayList()
                    // Long running Background task
                     .. progress 
                      OnProgress.invoke(pair)

                     // resul success
                    onSuccess.invoke(mOutputFilePaths)

                }


            }.onFailure {
                withContext(Dispatchers.Main) {
                    onFailure.invoke(it.localizedMessage)
                }
            }
        }
    }

}

fun cancelJob() {
    mainJob?.cancel()
  }
}

这是我正在启动我的 ViewModel

 val viewModel: SelectionViewModelby lazy {
    ViewModelProviders.of(this).get(SelectionViewModel::class.java)
}

当我开始工作时,我调用以下方法

viewModel.performAction(fileOptions,{success->},{failure->},{progress->})

当我想取消任务时。我调用以下方法。

viewModel.cancelJob()

问题是即使在取消作业后,我仍然会收到正在调用的进度。这意味着作业没有被取消。我想实施正确的方法来启动和取消作业,同时保留在视图模型范围内。

那么实现视图模型以启动和取消作业的正确方法是什么?

标签: androidviewmodelkotlin-coroutinesandroid-viewmodel

解决方案


为了取消作业,您必须有一个暂停函数调用。

这意味着如果你的工作有类似的代码

while (canRead) {
   read()
   addResults()
}
return result

这永远不能以您希望的方式取消。

有两种方法可以取消此代码

a)添加延迟功能(这将检查取消并取消您的工作)

b)(在上述情况下是正确的方法)定期添加一个 yield() 函数

所以上面的代码应该是这样的:

while(canRead) {
    yield()
    read()
    addResults()
}
return result

编辑:可能需要一些进一步的解释来说明这一点

仅仅因为您使用上下文运行某些东西,并不意味着协程可以随时停止或中断它

协程所做的基本上是用回调改变旧的做事方式,并用挂起函数代替它

我们过去对复杂计算所做的是启动一个线程,该线程将执行计算,然后得到一个带有结果的回调。

在任何时候你都可以取消线程并且工作会停止。

取消协程不一样

如果你取消一个协程,你基本上要做的就是告诉它工作被取消,并且在下一个合适的时刻它应该停止

但如果你不使用 yield() delay() 或任何暂停功能,这样的时机永远不会到来

它相当于用线程运行这样的东西

while(canRead && !cancelled) {
   doStuff
}

您将手动设置取消标志的位置,如果您设置它但没有在代码中检查它,它将永远不会停止

作为旁注,要小心,因为现在你有一大块计算正在运行代码,这将在一个线程上运行,因为你从未调用过挂起函数。当您添加 yield() 调用时,它可能会更改线程或上下文(在您定义的 ofc 范围内),因此请确保它是线程安全的


推荐阅读