Что такое `Task` в Swift Concurrency?

Ответ

Task — это фундаментальная единица асинхронной работы в современной модели конкурентности Swift (Swift Concurrency). Она представляет собой изолированную задачу, которая выполняется асинхронно и может быть отменена.

Для чего используется?

  • Для запуска асинхронного кода из синхронного контекста.
  • Для управления жизненным циклом и отменой асинхронных операций.
  • Для организации структурированного параллелизма.

Базовое создание задачи:

// Запуск новой асинхронной задачи
Task {
    // Этот код выполняется асинхронно в рамках cooperative thread pool
    let image = await downloadImage(from: url)
    await MainActor.run { // Возврат на главный поток для обновления UI
        self.imageView.image = image
    }
}

Типы задач и управление:

  1. Структурированные задачи: Создаются с помощью async let или withTaskGroup. Их жизненный цикл привязан к родительской задаче.

    func fetchMultipleResources() async throws -> (Data, Data) {
        // Параллельный запуск двух асинхронных операций
        async let firstData = fetchData(from: url1)
        async let secondData = fetchData(from: url2)
        // Ожидание завершения обеих задач
        return try await (firstData, secondData)
    }
  2. Неструктурированные задачи (Task.detached): Не наследуют контекст родителя (приоритет, актор) и живут независимо.

    Task.detached(priority: .background) {
        let result = await heavyBackgroundProcessing()
        print(result)
    }
  3. Отмена задачи:

    let task = Task {
        try await Task.sleep(nanoseconds: 2_000_000_000)
        print("Эта строка не выполнится, если задача отменена")
    }
    // ... позже
    task.cancel() // Запрос на отмену
    
    // Внутри задачи можно проверять статус отмены
    if Task.isCancelled { return }
    // или использовать Task.checkCancellation(), который выбросит ошибку при отмене

Важно: Задачи выполняются на cooperative thread pool, управляемом рантаймом. Они не блокируют потоки, а приостанавливаются (await), освобождая поток для других задач.

Ответ 18+ 🔞

О, так, слушай, смотри, сейчас я тебе про Task так расскажу, что ты офигеешь. Это, блядь, фундамент всей этой современной асинхронной движухи в Swift. Представь себе маленького работягу-гномика, которого ты запускаешь в параллельную вселенную, чтобы он там что-то сделал, пока ты тут чай пьёшь. И главное — ты можешь ему крикнуть «СТОЙ, НАХУЙ!», и он, если не упёртый, остановится. Вот это и есть задача.

Зачем это, спросишь? Ну, ёпта:

  • Чтобы из обычного, тупого синхронного кода запустить что-то асинхронное, не вызывая у системы эпилепсию.
  • Чтобы управлять этим гномиком: запустить, а потом, если передумал, — отменить, чтобы он не жрал ресурсы почём зря.
  • Чтобы строить, блядь, структурированный параллелизм, а не бардак из completion-блоков, который потом разгребать неделю.

Вот, смотри, как его, падлу, создают:

// Запускаем новую асинхронную задачу
Task {
    // Всё, что тут — делается где-то там, в параллельном измерении, на каком-то из потоков пула
    let image = await downloadImage(from: url) // Тут гномик приостановится и не будет никого ебать
    await MainActor.run { // А тут он вернётся на главный поток, чтобы UI не сломал
        self.imageView.image = image
    }
}

А теперь про виды этих гномиков, их же дохуя:

  1. Структурированные (воспитанные): Они привязаны к родителю. Родитель отменился — и они все пошли нахуй. Создаются через async let или withTaskGroup.

    func fetchMultipleResources() async throws -> (Data, Data) {
        // Запускаем двух гномиков ПАРАЛЛЕЛЬНО, чтобы не ждать по очереди, как лохи
        async let firstData = fetchData(from: url1)
        async let secondData = fetchData(from: url2)
        // Сидим и ждём, пока оба не отпинаются
        return try await (firstData, secondData)
    }
  2. Неструктурированные, отвязанные ублюдки (Task.detached): Им похуй на родителя. Они не наследуют ни его приоритет, ни актор. Запустил и забыл, как в старые добрые времена.

    Task.detached(priority: .background) { // Отправил в глубокий бэкграунд, пусть там ковыряется
        let result = await heavyBackgroundProcessing()
        print(result)
    }
  3. А вот самое сочное — ОТМЕНА. Ты ж не хочешь, чтобы гномик таскал кирпичи, если тебе уже не надо?

    let task = Task {
        try await Task.sleep(nanoseconds: 2_000_000_000) // Спит 2 секунды
        print("Эта строка не выполнится, если задача отменена") // А вот это может и не случиться!
    }
    // ... а через секунду тебе передумалось
    task.cancel() // Щёлк выключателем — запрос на отмену отправлен!
    
    // А внутри задачи гномик может проверять, не послали ли его уже:
    if Task.isCancelled { return } // Типа «ой, всё»
    // Или вызвать `try Task.checkCancellation()` — это выбросит ошибку, если отмена была. Удобно для пропагации.

И главное, запомни, ёпта: Эти задачи выполняются на cooperative thread pool. Это не те старые потоки, которые тупо блокируются. Гномик доходит до await — говорит «окей, я подожду» — и ВЫСВОБОЖДАЕТ поток, чтобы другие гномики тоже могли поработать. Красота, а не система! Пиздец как грамотно придумано.