Как дождаться результата двух параллельных задач в Kotlin, если одна упала или ушла в таймаут, но нужно получить результат второй
Что проверяют
Понимаете ли вы structured concurrency: что падение одного async делает с соседями и с родителем. Следом спрашивают, почему try/catch вокруг await() не спасает и как при этом не сломать отмену корутины.
Как отвечать
Сначала объясните ловушку. Внутри coroutineScope обычное исключение (не отмена) в любом дочернем async отменяет весь скоуп вместе со второй задачей, даже если вы поймали его на await(). Код с try/catch вокруг deferred1.await() результат второй задачи не получит.
Рабочие варианты:
supervisorScope: падение одного ребёнка не трогает соседей, исключение прилетает только в егоawait(). Каждыйawait()заворачиваете отдельно и получаете вторую задачу независимо от первой;- таймаут без исключения:
withTimeoutOrNull(3.seconds) { repo.loadA() }вернётnull, если не успели.withTimeoutбросаетTimeoutCancellationException, наследникаCancellationException, и его легко перепутать с настоящей отменой; - ошибка как значение: внутри каждого
asyncоборачиваете вызов и возвращаетеResultили sealed-класс, тогда наружу ничего не вылетает и хватает обычногоcoroutineScope; - Android:
viewModelScopeпостроен наSupervisorJobиDispatchers.Main.immediate, но дети внутри одногоlaunchсвязаны обычнымJob, так чтоsupervisorScopeвсё равно нужен. Suspend-вызовы Retrofit главный поток не блокируют, а блокирующий код уводите вwithContext(Dispatchers.IO).
Для экрана собирайте оба результата в состояние, где каждая часть может быть данными, ошибкой или таймаутом, и показывайте то, что пришло.
Где спотыкаются
Ловят Exception или используют runCatching и глотают CancellationException. Пользователь ушёл с экрана, а корутина работает дальше, будто отмены не было. Отмену нужно пробрасывать.
Спасаются от отмены через GlobalScope или отдельный CoroutineScope(Job()). Ошибка изолирована, но задачи переживают экран и текут.
Считают, что withTimeout прерывает любой код. Отмена кооперативная: Thread.sleep или блокирующий сокет она не остановит, нужны suspend-API или runInterruptible.