#android #viewmodel #kotlin-coroutines #android-viewmodel
#Android #viewmodel #kotlin-сопрограммы #android-viewmodel
Вопрос:
У меня есть задание внутри моего AndroidViewModel
класса. Задание запускается viewModelScope.launch
. Задание — это длительный процесс, который возвращает результат с помощью лямбда-функций. В соответствии с требованием, если пользователь хочет отменить задание, оставаясь в области действия при нажатии кнопки, он должен отменить задание. Проблема в том, что когда я отменяю задание, процесс все еще выполняется в фоновом режиме и вычисляет фоновую задачу. Ниже приведен мой 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()
Проблема в том, что даже после отмены задания я все еще получаю прогресс по мере его вызова. Это означает, что задание не было отменено.
Я хочу реализовать правильный способ запуска и отмены задания, оставаясь в области viewmodel.
Итак, как правильно реализовать viewmodel для запуска и отмены задания?
Комментарии:
1. Может быть, попробуйте проверить
isActive
внутри вашей длительной задачи, а не за ее пределами
Ответ №1:
Чтобы отменить задание, у вас должен быть приостановленный вызов функции.
Это означает, что если ваша работа имеет код, подобный
while (canRead) {
read()
addResults()
}
return result
это никогда не может быть отменено так, как вы хотите, чтобы оно было отменено.
есть два способа отменить этот код
a) добавьте функцию задержки (это проверит отмену и отменит ваше задание)
б) (что в приведенном выше случае является правильным способом) периодически добавляйте функцию yield ()
итак, приведенный выше код должен выглядеть следующим образом:
while(canRead) {
yield()
read()
addResults()
}
return result
редактировать: вероятно, необходимы некоторые дополнительные пояснения, чтобы прояснить это
то, что вы запускаете что-то с помощью context, не означает, что сопрограммы могут остановить или прервать его в любое время
что делают сопрограммы, так это в основном меняют старый способ выполнения обратных вызовов и заменяют его приостанавливающими функциями
для сложных вычислений мы обычно запускали поток, который выполнял вычисления, а затем получал обратный вызов с результатами.
в любой момент вы можете отменить поток, и работа остановится.
отмена сопрограмм — это не то же самое
если вы отменяете сопрограмму, что вы в основном делаете, это сообщаете ей, что задание отменено, и в следующий подходящий момент оно должно прекратиться
но если вы не используете yield() delay () или любую функцию приостановки, такой подходящий момент никогда не наступит
это эквивалентно запуску чего-то подобного с потоками
while(canRead amp;amp; !cancelled) {
doStuff
}
если вы вручную установили флаг отмены, если вы его установили, но не проверили в своем коде, он никогда не остановится
в качестве примечания, будьте осторожны, потому что прямо сейчас у вас есть большой блок вычислений, выполняющих код, это будет выполняться в одном потоке, потому что вы никогда не вызывали приостанавливающую функцию. Когда вы добавляете вызов yield() , он может изменять потоки или контекст (в пределах того, что вы определили ofc), поэтому убедитесь, что он потокобезопасен
Комментарии:
1. Спасибо за ваш подробный ответ. Это очень помогло мне понять проблему и реализовать ее в соответствии с моим требованием.
2. Действительно, очень хороший ответ! У меня есть вопрос, а
yield()
не могли бы выwhile (canRead amp;amp; coroutineContext[Job].isActive) {..}
проявить сотрудничество и позволить отменить корутин, когда это необходимо?3. Я почти уверен, что вы можете сделать это в любом случае, я предпочитаю метод yield, потому что он вызывает исключение CancellationException