Программирование SwiftКонкурентностьРазработчик приложений на Swift

Сравните повторное ожидание одного дескриптора Task с новым запуском асинхронной операции.

Сравните повторное ожидание одного дескриптора Task с новым запуском асинхронной операции.

Проходите собеседования с ИИ помощником Hintsage

Краткий ответ

Повторное ожидание одного и того же дескриптора Task не запускает работу заново: все ожидающие получают один сохранённый результат или одну сохранённую ошибку. Новый запуск асинхронной операции создаёт другое выполнение и может привести к повторному побочному эффекту.

Исторический контекст

Модель async/await отделяет описание асинхронной операции от управления её выполнением. Дескриптор Task нужен не только для запуска работы, но и для последующего присоединения к уже выполняющемуся вычислению.

Такой подход решает типичную проблему callback-моделей: несколько потребителей могут ждать один результат без ручного распределения callback-функций и без запуска одинаковой операции несколько раз.

Постановка проблемы

Предположим, несколько экранов одновременно запрашивают один ресурс. Если каждый вызов запускает собственную асинхронную операцию, возникнут дублирование сетевых запросов, лишняя нагрузка и возможные гонки при обновлении общего состояния.

Если же сохранить один дескриптор Task и передать его потребителям, они присоединятся к одному вычислению. Важно отличать это от повторного вызова функции, которая создаёт новый Task: такой вызов начнёт отдельную операцию.

Подробное решение

Task представляет конкретный экземпляр выполняемой работы. После завершения его результат сохраняется внутри этого экземпляра, поэтому последующее обращение к value получает тот же результат без повторного выполнения тела задачи.

func demo() async { let task = Task { print("работа выполнена") return 42 } let first = await task.value let second = await task.value print(first, second) }

В примере сообщение о работе печатается один раз, а оба ожидания получают значение 42. Если задача завершается ошибкой, повторное ожидание того же дескриптора снова наблюдает завершение этой задачи с той же ошибкой.

Это не означает, что Task автоматически является полноценным кэшем бизнес-данных. Дескриптор нужно сохранить и сделать доступным потребителям; после его замены новой задачей последующие вызовы могут присоединиться уже к другому вычислению.

Отмена также требует отдельного внимания. Отмена ожидающего потребителя не означает автоматическую отмену общей задачи-производителя, а флаг отмены сам по себе не прерывает произвольный код. Производитель должен сотрудничать с отменой, а владелец дескриптора должен явно определить, когда общую работу следует отменять.

Ситуация из практики

Сервис загрузки изображений получает пять одновременных запросов на один URL. Вариант с запуском отдельного Task для каждого запроса прост, но создаёт пять сетевых операций. Вариант с глобальным словарём готовых данных экономит запросы, однако не решает проблему параллельных промахов: несколько потребителей могут одновременно не найти запись и всё равно начать загрузку.

Практичное решение — хранить в словаре не только готовые изображения, но и дескрипторы текущих задач загрузки. Первый запрос создаёт задачу, остальные получают тот же дескриптор и ожидают его value. После завершения результат можно перенести в кэш, а запись о выполняющейся задаче удалить; это предотвращает бесконечное удержание завершённых задач и позволяет корректно повторить неудачную загрузку.

Что кандидаты часто упускают

  1. Запускается ли тело задачи заново после завершения первого ожидания?

Нет. Ожидание task.value присоединяется к конкретному экземпляру Task. После завершения его результат или ошибка сохраняются, поэтому последующие ожидания не повторяют тело задачи и не воспроизводят её побочные эффекты.

  1. Отменится ли общая задача, если один из её потребителей отменён?

Нет, автоматически это не гарантируется. Отмена потребителя изменяет состояние именно этой задачи; дескриптор общей задачи не получает от этого обязательную отмену. Если общая работа больше не нужна, приложение должно явно определить политику: отменять её при отсутствии потребителей или продолжать ради кэша.

  1. Можно ли использовать дескриптор после завершения задачи?

Да. Его value можно ожидать после завершения, и будет возвращён уже сохранённый результат либо повторно сообщена ошибка. Однако сохранение дескриптора само по себе не превращает результат в бессрочный кэш: оно удерживает состояние задачи, а правила хранения, устаревания и повторной загрузки нужно реализовать отдельно.