Почему этот фильтр продолжает жечь CPU после закрытия экрана?
Пиксельный фильтр работает в viewModelScope. Когда пользователь уходит с экрана, ViewModel очищается и scope отменяется, но цикл продолжает жечь CPU до последнего пикселя.
Ограничения: цикл — чистая CPU-работа без приостанавливающих вызовов внутри, корутина должна остановиться примерно за одну итерацию после отмены, а результат — остаться корректным.
fun applyFilter(pixels: IntArray) = viewModelScope.launch(Dispatchers.Default) {
for (i in pixels.indices) {
pixels[i] = heavyTransform(pixels[i])
}
_result.value = pixels
}
Найдите и исправьте ошибку.
Отмена кооперативна: она срабатывает лишь в точке приостановки или на явной проверке, а в цикле нет ни того, ни другого — поэтому он доходит до конца. Проверяйте каждую итерацию через ensureActive() или yield() либо оберните цикл в while (isActive).
- ✗Считать, что отменённую корутину останавливают принудительно в произвольном месте
- ✗Думать, что отмену «доставляет» диспетчер, а не точка приостановки
- ✗Полагать, что CPU-цикл сам прерывается между итерациями
- →Чем
ensureActive()отличается здесь от чтенияisActive? - →Что даст здесь
yield()сверх проверки на отмену?
Баг
Отмена в корутинах кооперативна. cancel() лишь переводит Job в отменённое состояние — он не может выдернуть поток из выполняемого кода. Отмена «срабатывает» только там, где корутина приостанавливается или сама спрашивает о своём состоянии:
for (i in pixels.indices) {
pixels[i] = heavyTransform(pixels[i]) // ни одной точки приостановки
}
Здесь нет ни suspend-вызова, ни проверки — значит, цикл честно досчитает все пиксели, уже будучи отменённым, и только потом корутина завершится.
Исправление
Достаточно одной проверки на итерацию:
fun applyFilter(pixels: IntArray) = viewModelScope.launch(Dispatchers.Default) {
for (i in pixels.indices) {
ensureActive() // бросит CancellationException после отмены
pixels[i] = heavyTransform(pixels[i])
}
_result.value = pixels
}
ensureActive() бросает CancellationException, как только Job отменён, — цикл останавливается на следующей же итерации. yield() делает то же самое и вдобавок отдаёт поток другим корутинам (полезно, если пул занят). Вариант while (isActive) завершает цикл тихо, без исключения, — это подходит, когда частичный результат сам по себе допустим.