Лучший способ объяснить разницу - это объяснить механизм coroutineScope
.Рассмотрим этот код:
suspend fun main() = println(compute())
suspend fun compute(): String = coroutineScope {
val color = async { delay(60_000); "purple" }
val height = async<Double> { delay(100); throw HttpException() }
"A %s box %.1f inches tall".format(color.await(), height.await())
}
compute()
извлекает из сети две вещи и объединяет их в описание строки.В этом случае первая выборка занимает много времени, но в итоге получается успешной;второй сбой почти сразу после 100 миллисекунд.
Какое поведение вы хотели бы для приведенного выше кода?
Хотели бы вы на минутку color.await()
только для того, чтобы понять, что другой сетевой вызов давно потерпел неудачу?
Или, возможно, выХотели бы вы, чтобы функция compute()
поняла через 100 мс, что один из ее сетевых вызовов завершился неудачно и сразу же завершился ошибкой?
С supervisorScope
вы получаете 1., сcoroutineScope
вы получаете 2.
Поведение 2. означает, что, хотя async
само по себе не выбрасывает исключение (оно просто завершает Deferred
, которое вы получили от него),сбой немедленно отменяет его сопрограмму, которая отменяет родительский элемент, который затем отменяет все остальные дочерние элементы.
Такое поведение может быть странным, когда вы об этом не знаете.Если вы поймаете исключение из await()
, вы будете думать, что восстановились после него, но не сделали этого.Весь объем сопрограмм все еще отменяется.В некоторых случаях есть законная причина, по которой вы этого не хотите: именно тогда вы будете использовать supervisorScope
.