Ответ
В structured concurrency Swift переключение между задачами управляется кооперативной (cooperative) моделью многозадачности на уровне потоков. Система решает, когда приостановить (suspend) одну задачу и возобновить (resume) другую, основываясь на приоритетах и доступности потоков.
Ключевой принцип: Переключение происходит только в точках await.
func fetchUserData() async throws -> User {
// 1. Задача выполняется на текущем потоке.
let userRequest = URLRequest(url: userURL)
// 2. При достижении `await` задача может быть ПРИОСТАНОВЛЕНА.
// Система освобождает текущий поток для других задач.
let (userData, _) = try await URLSession.shared.data(for: userRequest)
// 3. Когда данные загружены, система ВОЗОБНОВЛЯЕТ эту задачу.
// Это может произойти на ДРУГОМ потоке из пула.
return try JSONDecoder().decode(User.self, from: userData)
}
func fetchUserAndPosts() async throws {
// Запускаем две дочерние задачи
async let user = fetchUserData()
async let posts = fetchPostsData()
// Ожидаем (`await`) результаты обеих задач.
// Пока они выполняются (ожидают сетевого ответа), текущая задача приостановлена,
// а её поток может выполнять другие задачи.
let (fetchedUser, fetchedPosts) = try await (user, posts)
print(fetchedUser, fetchedPosts)
}
Как это работает под капотом:
- Пул потоков: Swift runtime использует пул рабочих потоков, оптимизированный под количество CPU-ядер.
- Приостановка (
suspend): При встречеawait, если результат не готов немедленно (например, ожидание сети или сна), задача отмечается как приостановленная и освобождает занимаемый поток. - Возобновление (
resume): Когда ожидаемое значение становится доступным, система планирует возобновление задачи. Она будет выполнена на первом доступном потоке из пула (не обязательно на том же). - Приоритеты: Задачи наследуют приоритет от родителя (
.userInitiated,.utilityи т.д.), что влияет на порядок планирования. - Отмена: Отмена родительской задачи автоматически распространяется на все дочерние.
Важное следствие: Поскольку переключение происходит только в await, длительные синхронные вычисления внутри задачи могут блокировать поток. Для CPU-интенсивных операций используйте Task.detached или явно отпускайте поток с помощью await Task.yield().