В каком потоке выполняется async-метод, вызванный внутри Task, созданного в обычной (не изолированной) функции?

Ответ

Он выполняется в произвольном потоке из cooperative thread pool Swift Concurrency.

Ключевые принципы:

  1. Наследование контекста: Task наследует актор-контекст места своего создания. Если он создан вне MainActor (в обычной синхронной функции), у него нет привязки к главному потоку.
  2. Пул потоков: Система Swift Concurrency выбирает для выполнения тела async-функции любой доступный поток из своего пула.

Пример:

// 1. Обычная синхронная функция, не изолированная к MainActor.
func startBackgroundWork() {
    Task { // Эта задача НЕ привязана к MainActor.
        // Код до `await` выполняется в потоке из пула (A).
        print(Thread.current) // Не главный
        await asyncOperation()
        // Код после `await` может выполниться в том же или ДРУГОМ потоке из пула (B).
    }
}

// 2. Async-функция, вызванная внутри задачи.
func asyncOperation() async {
    // Выполняется в потоке из пула Swift Concurrency.
    let result = await downloadData()
}

Как управлять потоком?

  • Используйте @MainActor для гарантии выполнения на главном потоке.
  • Используйте глобальные акторы (@GlobalActor) для собственных изолированных контекстов.
  • Task.detached явно создает задачу, не наследующую контекст родителя.

Ответ 18+ 🔞

Давай разберём эту хуйню про потоки в Swift Concurrency, а то народ путается, как слепой кот в сортире.

Смотри, есть у нас эта штука — Task. Так вот, главный подвох в том, что эта сука наследует контекст, где её родили. Если ты её создал в обычной, не изолированной функции, то она НЕ ПРИВЯЗАНА к главному потоку. Вообще. Ноль. Ни хуя. Она как бездомный щенок — система возьмёт её и кинет в первый попавшийся свободный поток из своего пула.

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

func startBackgroundWork() {
    Task { // Эта задача — сирота, MainActor её не усыновлял.
        // Тут код до первого `await` выполнится в каком-то потоке из пула. Допустим, в потоке A.
        print(Thread.current) // И тут будет НЕ главный поток, запомни.
        await asyncOperation()
        // А вот после `await` система может взять и перекинуть выполнение в совершенно другой поток из пула. Например, в поток B. Никаких гарантий, ёпта!
    }
}

И вот эта asyncOperation() внутри — она тоже будет болтаться по этим потокам из пула, как мартышка по лианам.

Так как же, блядь, этим управлять, если всё так пиздецово?

А вот так:

  1. @MainActor — твой бро. Помечай им всё, что должно крутиться строго на главном потоке. UI-апдейты, например. Система тогда сама, как хитрая жопа, будет переключать контекст.
  2. Глобальные акторы (@GlobalActor) — если тебе нужен свой, особый контекст для изоляции, а не просто главный поток.
  3. Task.detached — это когда ты говоришь: «Знаешь что? Нахуй наследование контекста! Я сам по себе мужик!». Задача запустится независимо, без привязки к контексту родителя. Полезно, но осторожно — можно и в просак сесть.

Короче, запомни: Swift Concurrency — не про ручное управление потоками. Это про то, чтобы ты описал, где (в каком актор-контексте) должен выполняться код, а система уже сама разберётся, на каком конкретном потоке его гонять. А если не опишешь — будет гонять где попало, и потом удивляйся, почему UI лагает.