4. Отмена задач
Изучение данного блока предполагает предварительное знание синтаксиса языка Swift. Для успешного освоения этого материала, необходимо иметь базовое понимание синтаксиса языка Swift. Это включает в себя знание основных структур данных, операторов, циклов, функций, абстракций и других ключевых элементов языка. Без этих фундаментальных знаний будет сложно понять более сложные концепции и примеры, которые будут рассматриваться в данном блоке.
Мы продолжаем погружаться в работу с задачами. Следующий шаг - это отмена и правильная обработка состояния отмены. Ответственность за отмену задач лежит на разработчике.
Способы отмены задач¶
В парадигме Structured Concurrency мы постоянно переключаемся между состояниями. Поэтому если задача была отменена, то было бы хорошо прекратить её дальнейшее исполнение.
- Cancel(). Если у нас есть ссылка на исполняемую задачу, то достаточно вызвать метод *cancel()
Важно понимать, что вызов этого метода только меняет флаг isCancelled в true. Больше ничего не происходит - это означает, что мы должны сами во время работы задачи проверять, не отменилась ли задача
- withTaskCancellationHandler - данный способ отмены задач очень похож на первый, но используется больше для работы с последовательностями событий. Его мы обсудим позже в главе (AsyncSequence)
let hardTask = Task { ... } // Отменим задачу через 3 секунды Task { try await Task.sleep(nanoseconds: 3_000_000_000) task.cancel() }
Проверка отменена ли задача¶
Есть два основных способа проверить отменена ли задача
- Task.isCancelled - проверить значение флага isCancelled. Данный способ удобен тем, что дальше мы можем определить поведение задачи.
Пример:
Мы скачиваем видео и пользователь отменил скачивание файла, в момент окончания загрузки. Мы должны отменить задачу, но что делать со скаченным файлом большого веса? Мы конечно можем его сохранить как при обычной загрузке, но не хочется занимать лишнее место на диске. Поэтому отловив отмену задачи - мы можем просто удалить файл и закончить выполнение задачи.
- Task.checkCancellation() - данный способ проверки задачи аналогичен пункту 1, но он выкидывает ошибку CancellationError, которую можно обработать в родительской задаче в блоке do catch
В примере выше видно, что мы выполним не больше 4х шагов, благодаря такой проверке мы не выполняем остальные 6 шагов, тем самым разгружая исполнитель от ненужной работы.
let hardTask = Task { for step in 0...10 { /// Проверяем не отменена ли задача guard !Task.isCancelled else { print("Task was cancelled") return } print("Выполнение шага - \(step)" try await Task.sleep(nanoseconds: 1_000_000_000) // Имитация нагрузки } } // Отменим задачу через 3 секунды Task { try await Task.sleep(nanoseconds: 3_000_000_000) task.cancel() }
Когда необходимо делать проверку?¶
Писать постоянно проверку в задачах - плохо, потому что это делает ваш код нечитабельным. Приведём несколько советов:
- Перед отображением контента пользователю. Желательно убедиться в том, что пользователю действительно нужна информация.
let imageTask = Task { let imageInfo = fetchImageInfo() let image = await network.fetchImage(with: imageInfo.id) let croppedImages = await cropService.crop(image: image) guard !Task.isCancelled else { await imageService.removeImage(with: imageInfo) return } await MainActor.run { state.croppedImages = croppedImages } } - В больших и тяжелых синхронных функциях, которые могут очень долго выполняться. Особенно хорошо это делать после вызова Task.yield
- В логически завершённых кусках кода. Завершилась одна большая дочерняя задача и перед началом выполнение другой хорошо бы выполнить проверку.
Выше перечисленные советы, это только наблюдения автора, конечно вы всегда можете выставить проверку - где считаете необходимым.