Перейти к содержанию

3. Асинхронные функции и await


Изучение данного блока предполагает предварительное знание синтаксиса языка Swift. Для успешного освоения этого материала, необходимо иметь базовое понимание синтаксиса языка Swift. Это включает в себя знание основных структур данных, операторов, циклов, функций, абстракций и других ключевых элементов языка. Без этих фундаментальных знаний будет сложно понять более сложные концепции и примеры, которые будут рассматриваться в данном блоке.


Задачи создавать научились, поняли основную разницу между Task и Task.detached. Но что будет лежать внутри этих задач? Как этим управлять и как с этим работать?

Оглавление

Async Функции

Одно из назначений задач - это организация работы асинхронных функций. Функция помеченная ключевым словом async не вернёт результат сразу же. Она его вернёт через некоторое время. Именно поэтому данную функцию нельзя просто вызвать из синхронного кода. Такие функции могут вызываться либо в таких же асинхронных функциях, либо в Task.

func fetchUserFromDatabase() async throws -> User {
    .....
}

async - означает, что функция должна быть вызвана из асинхронного контекста.
throws - означает, что функция может выкинуть исключение

Пример

Task {
    do {
        let user = try await fetchUserFromDatabase()
        let userAvatar = try await getAvatar(userId: user.userId)
        /// Последовательное исполнение задач, сначала загрузится пользователь,
        /// затем начнётся подгрузка аватара пользователя
    } catch {
        /// Обработка ошибок
    }
}

Await

В примере выше мы уже использовали ключевое слово await. Вы скорее всего догадываетесь, что await нужен для того чтобы дождаться результата выполнения асинхронной функции. Но что именно происходит в момент вызова await?

await - это место, в котором останавливается выполнение кода функции - Suspention point. Остановка происходит до тех пор пока написанная после await функция не вернёт какое-нибудь значение. Что происходит с потоком на котором выполнялась функция? Если код приостанавливается и ждёт выполнения функции, то где он ждёт?

На самом деле, когда мы пишем Await происходит следующее:

  1. Создаётся состояние (continuation), в которое захватываются необходимые переменные, ссылки
  2. Созданное состояние передаётся исполнителю на выполнение
  3. Вместе с передачей состояния осуществляется и передача освободившегося потока. Далее уже исполнитель сам будет решать какую задачу запускать на этом потоке. Исполнитель также учитывает приоритет поступающих к нему задач.

Из выше описанных пунктов следует, что после того как мы вернёмся в остановленную функцию с результатом выполнения, то поток будет отличаться от потока с которого начиналось выполнение.

Task {
    /// Здесь мы были например на Thread 5
    let user = try await fetchUserFromDatabase()
    /// Здесь мы уже будет на другом потоке (иногда можем попасть и на тот же самый)
}

Task.yield

Вызов асинхронных функций через await позволяет исполнителю активно переключаться между состояниями. Но иногда бывают случаи, когда после вызова await следует очень тяжелая синхронная работа, которая не прерываясь исполняется в течение длительного времени

Task {
    let user = try await fetchUserFromDatabase()
    doSomeRealyHardWork() // предположим что внутри задачи находится тяжелый алгоритм
}
Данный кейс часто можно встретить на практике. Лучшим решением в такой ситуации будет приостановка выполнение синхронного кода с помощью Task.yield.

Вызов статичной функции yield позволяет приостановить на некоторое время выполнение синхронной функции и пропустить другие высокоприоритетные задачи вперёд. Такие синхронные функции очень часто имеют низкий приоритет выполнения и в случае огромной загрузки системы, вызов yield позволяет избежать возможных Инверсий приоритетов.

💡 Рекомендация

Если синхронная функция содержит внутри себя тяжёлую работу, то необходимо разбить её на части. Между частями задачи вызывать функцию yield

Task {
    let user = try await fetchUserFromDatabase()
    doFirstPartOfHardWork()
    await Task.yield()
    doSecondPartOfHardWork()
    await Task.yield()
    doFinalPartOfHardWork()
}