Мы продолжаем погружаться в работу с задачами. Следующий шаг - это отмена и правильная обработка состояния отмены. Ответственность за отмену задач лежит на разработчике.

Способы отмены задач

В парадигме Structured Concurrency мы постоянно переключаемся между состояниями. Поэтому если задача была отменена, то было бы хорошо прекратить её дальнейшее исполнение.

  1. Cancel(). Если у нас есть ссылка на исполняемую задачу, то достаточно вызвать метод cancel() Важно понимать, что вызов этого метода только меняет флаг isCancelled в true. Больше ничего не происходит - это означает, что мы должны сами во время работы задачи проверять, не отменилась ли задача
  2. withTaskCancellationHandler - данный способ отмены задач очень похож на первый, но используется больше для работы с последовательностями событий. Его мы обсудим позже в главе (AsyncSequence)
let hardTask = Task { ... }

// Отменим задачу через 3 секунды
Task {
    try await Task.sleep(nanoseconds: 3_000_000_000)
    task.cancel()
}

Проверка отменена ли задача

Есть два основных способа проверить отменена ли задача

  1. Task.isCancelled - проверить значение флага isCancelled. Данный способ удобен тем, что дальше мы можем определить поведение задачи.
  1. Task.checkCancellation() - данный способ проверки задачи аналогичен пункту 1, но он выкидывает ошибку CancellationError, которую можно обработать в родительской задаче в блоке do catch
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()
}

В примере выше видно, что мы выполним не больше 4х шагов, благодаря такой проверке мы не выполняем остальные 6 шагов, тем самым разгружая исполнитель от ненужной работы.

Когда необходимо делать проверку?

Писать постоянно проверку в задачах - плохо, потому что это делает ваш код нечитабельным. Приведём несколько советов:

  1. Перед отображением контента пользователю. Желательно убедиться в том, что пользователю действительно нужна информация.
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
   }
}