AndroidViewModel отменить задание по требованию

#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