#kotlin #kotlin-coroutines
#kotlin #kotlin-сопрограммы
Вопрос:
В сопрограммах, когда я хочу защитить блок кода от отмены, я должен добавить NonCancellable в контекст:
@Test
fun coroutineCancellation_NonCancellable() {
runBlocking {
val scopeJob = Job()
val scope = CoroutineScope(scopeJob Dispatchers.Default CoroutineName("outer scope"))
val launchJob = scope.launch(CoroutineName("cancelled coroutine")) {
launch (CoroutineName("nested coroutine")) {
withContext(NonCancellable) {
delay(1000)
}
}
}
scope.launch {
delay(100)
launchJob.cancel()
}
launchJob.join()
}
}
Выполнение приведенного выше модульного теста займет ~ 1,1 секунды, даже если длительная сопрограмма будет отменена всего через 100 мс. Это эффект NonCancellable, и я понимаю этот момент.
Однако приведенный ниже код, по-видимому, функционально эквивалентен:
@Test
fun coroutineCancellation_newJobInsteadOfNonCancellable() {
runBlocking {
val scopeJob = Job()
val scope = CoroutineScope(scopeJob Dispatchers.Default CoroutineName("outer scope"))
val launchJob = scope.launch(CoroutineName("cancelled coroutine")) {
launch (CoroutineName("nested coroutine")) {
withContext(Job()) {
delay(1000)
}
}
}
scope.launch {
delay(100)
launchJob.cancel()
}
launchJob.join()
}
}
Я пытался найти какие-либо функциональные различия между этими двумя подходами с точки зрения отмены, обработки ошибок и общей функциональности, но пока я ничего не нашел. В настоящее время похоже, что NonCancellable находится в фреймворке только для удобства чтения.
Теперь важна удобочитаемость, поэтому я бы предпочел использовать NonCancellable в коде. Однако в его документации звучит так, как будто это, на самом деле, как-то отличается от обычного задания, поэтому я хочу разобраться в этом аспекте подробнее.
Итак, мой вопрос: есть ли какая-либо функциональная разница между этими двумя подходами (т. Е. Как я могу изменить эти модульные тесты, чтобы получить разницу в результатах)?
Редактировать:
Следуя ответу Луиса, я протестировал сценарий «сделать очистку не подлежащей отмене», и в этом случае Job() также работает аналогично NonCancellable. В приведенном ниже примере модульный тест будет выполняться более 1 секунды, даже если сопрограмма будет отменена сразу после 200 мс:
@Test
fun coroutineCancellation_jobInsteadOfNonCancellableInCleanup() {
runBlocking {
val scope = CoroutineScope(Job() Dispatchers.Default CoroutineName("outer scope"))
val launchJob = scope.launch(CoroutineName("test coroutine")) {
try {
delay(100)
throw java.lang.RuntimeException()
} catch (e: Exception) {
withContext(Job()) {
cleanup()
}
}
}
scope.launch {
delay(200)
launchJob.cancel()
}
launchJob.join()
}
}
private suspend fun cleanup() {
delay(1000)
}
Ответ №1:
NonCancellable
не реагирует на отмену, хотя Job()
и реагирует.
NonCancellable
реализуется Job
пользовательским способом, и он не имеет такого же поведения, как Job()
при использовании отменяемой реализации.
cancel()
on NonCancellable
— это no-op, в отличие Job()
от того, где он отменяет любую дочернюю сопрограмму, и где любой сбой в дочерних сопрограммах будет распространяться на этого родителя Job
.
Комментарии:
1. Спасибо за ответ. Это имеет смысл, но я пытаюсь найти практически значимую разницу. Можете ли вы придумать какой-либо разумный фрагмент кода, где это различие в поведении будет иметь значение?
2. Да:
NonCancellable
это единственный, который не подлежит отмене. Пример использования: задержка отмены в блоке catch all перед повторным перемещением. Для сопрограммы, которая воспроизводит звук, вам может потребоваться задержка на очень короткое время, чтобы сгладить прерывание звука, уменьшив громкость, вместо того, чтобы вызывать сбой звука. Все же следует иметь в виду, что это может задержать отмену за пределами области действия функции.3. Кроме того, если вы можете соответствовать
withContext(Job())
вашему варианту использования, это все еще более неясно, чемNonCancellable
потому, что у последнего есть более описательное имя.4. Как я уже писал в вопросе, я понимаю ценность удобочитаемости, но это не то, о чем я здесь спрашиваю. Я отредактировал вопрос и добавил один дополнительный тест в соответствии с вашим предложением (задержка в блоке catch). В этом случае Job() также работает аналогично NonCancellable .