Если в области нет SupervisorJob(), то сбой одного дочернего элемента сопрограммы приведет к сбою родительской области, а все остальные дочерние элементы родительской области завершатся ошибкой.
Если приведенное выше утверждение верно, то в примере кода child 2 coroutine
не следует печатать, поскольку первая сопрограмма завершается сбоем из-за выданного исключения.
Но я все еще получаю этот вывод:
child 1 coroutine
child 2 coroutine
В чем разница при использовании SupervisorJob с val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
?
val scope = CoroutineScope(Dispatchers.IO)
Button(onClick = {
scope.launch {
launch {
println("child 1 coroutine")
delay(2.seconds)
throw CancellationException()
}
delay(4.seconds)
launch {
println("child 2 coroutine")
}
}
}) {
Text(text = "Scope check")
}
ОТВЕТ: Для лучшего объяснения прочитайте ниже ответ, который отмечен как правильный от @Leviathan.
Код Crt для обработки исключения
val handler = CoroutineExceptionHandler { _, e ->
println("handled: ${e.message}")
}
val scope = CoroutineScope(Dispatchers.IO + handler)
Button(
modifier = Modifier.padding(innerPadding),
onClick = {
scope.launch {
supervisorScope {
launch {
println("child 1 coroutine")
delay(1.seconds)
throw Exception("Child 1 Exception")
}
delay(4.seconds)
launch {
println("child 2 coroutine")
}
}
}
}) {
Text(text = "Scope check")
}
@Raghunandan, если я выдаю обычное исключение, выбрасываю Exception («Сбой в дочернем элементе 1»), то приложение аварийно завершает работу. После попытки поймать тот же вывод
fun main() { runBlocking { launch { println("дочерняя сопрограмма 1") Delay(2000) throw Exception() } Delay(4000) launch { println("дочерняя сопрограмма 2") } } } попробуйте это на игровой площадке Kotlin. если первый ребенок выдает исключение, второй отменяется
Вы можете прочитать документ kotlinlang.org/docs/Exception-handling.html . Надеюсь, это поможет вам понять. Или вы можете посмотреть этого человека youtu.be/e7tKQDJsTGs?si=cOF4uAIHGK504wF4
Без SupervisorJob
: сбой дочерней сопрограммы (например, дочерней 1) приведет к отмене родительской области, и все остальные дочерние сопрограммы (например, дочерняя 2) также будут отменены.
С SupervisorJob
: сбой дочерней сопрограммы не влияет на другие дочерние сопрограммы.
Потому что: Job гарантирует, что в случае сбоя дочерней сопрограммы другие дочерние сопрограммы не будут отменены.
Без SupervisorJob по-прежнему печатается дочерний элемент 2, я обновил SS в QA, пожалуйста, проверьте
Сначала немного о CancellationException
— это особое исключение и по сути означает отмену сопрограммы, а не ее сбой. Когда сопрограмма отменяется, она завершается, но не отменяет своего родителя. throw CancellationException()
поскольку последняя строка в сопрограмме ничего не дает.
Теперь о разнице между использованием SupervisorJob()
и неиспользованием. Используя scope.launch
, вы создаете новую сопрограмму с новым контекстом сопрограммы и новым заданием, которое отменяется при сбое одного из его дочерних элементов. Итак, в вашем примере на самом деле не имеет значения, есть ли scope
SupervisorJob
или нет, потому что все, что он видит, - это одна неисправная дочерняя сопрограмма, созданная launch
.
Вероятно, вам нужно запускать дочерние сопрограммы в самом scope
. В приостановленной функции вы можете заменить scope.launch
на with(scope){
, scope.run{
или просто supervisorScope {
. В вашем случае вы можете увидеть разницу, обернув сопрограммы в контролируемую область сопрограммы внутри launch
:
val handler = CoroutineExceptionHandler { _, exception ->
println("Handler got $exception")
}
//----
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
CoroutineScope(SupervisorJob() + handler).run {
// OR
// CoroutineScope(handler).run {
launch {
println("child 1 coroutine")
delay(2.seconds)
throw RuntimeException()
}
delay(4.seconds)
launch {
println("child 2 coroutine")
}
}
}
}) {
Text(text = "Scope check")
}
Подробнее читайте в этой статье.
В вашем примере кода есть несколько проблем. Прежде чем я попытаюсь объяснить более подробно, давайте сначала выясним, как вызывать различные задействованные сопрограммы:
scope.launch
launch
, выдающее исключение.launch
Следующие заблуждения мешают вашему коду вести себя должным образом:
Добавление CancellationException
просто отменяет текущую работу и останавливает ее. Отмена распространяется только вниз по иерархии должностей, а не вверх. Вот почему родительское задание не видит неудачную сопрограмму. Поэтому вторая сопрограмма не затрагивается и завершается успешно.
Итак, что вам на самом деле нужно в вашем примере кода, так это сбой сопрограммы. Для этого вам нужно заменить CancellationException
любым другим исключением, например Exception()
.
Вторая сопрограмма запускается только после delay(4.seconds)
: если первая сопрограмма дает сбой через две секунды, вторая сопрограмма еще даже не запускается. Это можно легко исправить, переместив 4-секундную задержку внутри блока запуска второй сопрограммы. Ваш пример кода теперь должен выглядеть так:
scope.launch {
launch {
println("child 1 coroutine")
delay(2.seconds)
throw Exception("child 1 failed")
}
launch {
delay(4.seconds)
println("child 2 coroutine")
}
}
Теперь происходит то, что обе сопрограммы запускаются немедленно. Через две секунды первая сопрограмма завершается с ошибкой. Поскольку вы (еще) не указали SupervisorJob, неудачная дочерняя сопрограмма приводит к сбою родительской сопрограммы. И это приводит к отмене всех оставшихся дочерних сопрограмм, в данном случае второй сопрограммы. Это приводит к ожидаемому поведению: child 2 coroutine
никогда не печатается.
Когда вы затем измените область действия на использование SupervisorJob, вы можете подумать, что теперь будет напечатан child 2 coroutine
. Однако это не так: SupervisorJob защищает своих прямых дочерних элементов только в случае сбоя дочернего объекта. Поскольку вы предоставили SupervisorJob в область, защищены только сопрограммы, запущенные в этой области. Однако первая и вторая дочерние сопрограммы запускаются в области родительской сопрограммы, поэтому родительским для них является это задание, а не SupervisorJob. Исправить это можно любым из следующих вариантов:
scope.launch
.supervisorScope { /*...*/ }
.val supervisor = SupervisorJob()
) и предоставьте ее в качестве контекста для двух дочерних сопрограмм: launch(supervisor) { /*...*/ }
Хотя сбой первой сопрограммы больше не приводит к сбою родительской сопрограммы, поэтому вторая сопрограмма продолжает работать, фактическое исключение так и не было обработано. А поскольку это необработанное исключение поднимает иерархию заданий и никогда не находит обработчика, самое верхнее задание завершается с ошибкой, и все сопрограммы в иерархии отменяются, включая задание супервизора и, следовательно, также вторую сопрограмму. А затем необработанное исключение генерируется повторно, и ваше приложение аварийно завершает работу.
Чего вам не хватает, так это CoroutineExceptionHandler
, как описано в документации SupervisorJob
:
Сбой или отмена дочернего элемента не приводит к сбою задания супервизора и не влияет на другие его дочерние элементы, поэтому супервизор может реализовать специальную политику для обработки сбоев своих дочерних элементов:
- Сбой дочернего задания, созданного с помощью
launch
, можно обработать с помощьюCoroutineExceptionHandler
в контексте.[...]
Предположим, вы используете этот обработчик:
val handler = CoroutineExceptionHandler { _, e ->
println("handled: ${e.message}")
}
Затем вы можете предоставить обработчик в качестве контекста для всей области:
val scope = CoroutineScope(Dispatchers.IO + handler)
Или вы можете указать его только для родительской сопрограммы:
scope.launch(handler) { /*...*/ }
В обоих случаях исключение будет обработано, чтобы оно не всплывало в иерархии заданий и в конечном итоге не приводило к сбою всех заданий.
И только теперь вы получите ожидаемый результат:
child 1 coroutine
handled: child 1 failed
child 2 coroutine
Хорошее и понятное объяснение, но можете ли вы объяснить, получаем ли мы пользу, когда супервизорJob добавляется в coroutineScope(), с каким-нибудь примером, пожалуйста
просто создайте обычное исключение и проверьте