Можно ли отменить задачу в Grand Central Dispatch (GCD)?

Ответ

Прямого аналога cancel() для DispatchQueue нет, но отмену можно реализовать с помощью DispatchWorkItem. У этого объекта есть методы cancel() и свойство isCancelled.

Пример отмены задачи:

// 1. Создаем workItem с проверкой отмены внутри
let workItem = DispatchWorkItem {
    // Периодически проверяем, не отменена ли задача
    for i in 1...10 {
        // Выходим из цикла, если задача отменена
        if Thread.isCancelled { break } // Или workItem.isCancelled, если доступно
        print("Шаг (i)")
        sleep(1)
    }
}

// 2. Ставим задачу в очередь на выполнение
let queue = DispatchQueue.global(qos: .utility)
queue.async(execute: workItem)

// 3. Отменяем задачу через 3 секунды (до или во время выполнения)
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    workItem.cancel()
    print("Задача отменена")
}

Важные детали:

  • cancel() — кооперативный: Вызов workItem.cancel() только устанавливает флаг isCancelled. Он не прерывает уже выполняющийся блок кода. Код внутри DispatchWorkItem должен самостоятельно проверять этот флаг и корректно завершаться.
  • Отмена до выполнения: Если задача еще не начала выполняться, cancel() предотвратит её запуск.
  • Альтернатива: Для более сложных сценариев с зависимостями и приоритетами лучше подходит Operation и OperationQueue, где у Operation есть полноценный механизм отмены (cancel(), isCancelled).

Ответ 18+ 🔞

А, ну ты про отмену задач в DispatchQueue спросил. Так вот, слушай, прямого-то метода cancel() у самой очереди — нихуя нет, понимаешь? Как будто ты поставил чайник, а кнопки «выключить» на нём не предусмотрели, ёпта.

Но наши ребята-разработчики не лыком шиты, блядь. Они придумали костыль, но костыль годный — DispatchWorkItem. У этой штуки есть свойство isCancelled и метод cancel(). Всё, казалось бы, просто, но тут, сука, главный подвох.

Вот смотри, как это работает, на примере:

// 1. Лепим себе workItem, но внутри него надо быть не мудаком и проверять отмену
let workItem = DispatchWorkItem {
    // Тут мы делаем какую-то долгую хуйню, например, цикл
    for i in 1...10 {
        // А вот тут, блядь, самое важное! Надо постоянно спрашивать: "А не отменили ли меня, сука?"
        if Thread.isCancelled { break } // Или workItem.isCancelled, если доступно
        print("Шаг (i)")
        sleep(1) // Спим, как будто тяжёлая операция
    }
}

// 2. Кидаем эту задачку в какую-нибудь фоновую очередь
let queue = DispatchQueue.global(qos: .utility)
queue.async(execute: workItem)

// 3. А потом передумываем. Через 3 секунды: "А нахуй оно надо?"
DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
    workItem.cancel()
    print("Задача отменена, ебать!")
}

А теперь, блядь, лови главную мысль, чтобы не обосраться:

  • cancel() — он кооперативный, сука! Это не волшебная красная кнопка. Когда ты вызываешь workItem.cancel(), он просто тихонечко выставляет флажок isCancelled в true. И всё. Он НЕ ВЗРЫВАЕТ уже выполняющийся код нахуй. Код внутри DispatchWorkItem должен сам, как воспитанный человек, периодически проверять этот флаг и говорить: «О, меня отменили? Ну окей, закругляюсь». Если не проверять — будет работать до победного, хоть сто раз отменяй.
  • Если не успел стартануть — повезло. Если задача ещё болтается в очереди и не начала выполняться, то cancel() её аккуратно выкинет, и она не запустится. Красота.
  • Для сложных дел — есть тяжёлая артиллерия. Если у тебя там целый цирк с зависимостями задач, приоритетами и прочей хуйнёй, то забей на DispatchQueue и иди в Operation и OperationQueue. Там у каждой Operation есть нормальный, полноценный механизм отмены (cancel(), isCancelled), который уже встроен в саму логику. Это как перейти с самопальной дубины на швейцарский армейский нож, блядь.

Короче, запомни: хотел отменить — спроси сначала у задачи, готова ли она к отмене. А то получится, как в том анекдоте: «Я тебе послал мысленный приказ выйти из комнаты! Почему не вышел?».