Допустимо ли выполнять await внутри defer в async-функции?
Нет. Тело defer синхронное, поэтому оно не может содержать операцию await и приостановить выполнение. Для асинхронной очистки нужно явно вызвать её перед каждым выходом из области либо инкапсулировать такой жизненный цикл в отдельную асинхронную абстракцию.
defer решает задачу гарантированной синхронной очистки при выходе из области видимости: через обычный return, ошибку или другой переход управления. Его модель близка к конструкции finally, но не предусматривает приостановку выполнения.
С появлением async/await очистка ресурса иногда сама стала асинхронной. Однако defer не был превращён в асинхронный блок: выход из области должен оставаться предсказуемым и не создавать дополнительную точку приостановки.
Если попытаться выполнить асинхронное закрытие ресурса в defer, компилятор отклонит такой код: вызов await нельзя размещать в синхронном теле defer. Простая замена на запуск отдельной Task тоже не является эквивалентом: функция может завершиться раньше очистки, а ошибка очистки будет отделена от исходного потока ошибок.
Это особенно опасно для транзакций, сетевых сессий и ресурсов, для которых завершение операции должно произойти до возврата результата вызывающему коду.
defer выполняется только при выходе из своей области видимости, а не при каждой приостановке в await. Его тело не может быть async, поэтому асинхронную очистку необходимо вызвать явно в успешной и ошибочной ветках.
Такой код гарантирует, что close() будет завершён до выхода из use(), а исходная ошибка будет повторно выброшена. Недостаток — дублирование очистки; его обычно устраняют асинхронной функцией-обёрткой, которая принимает операцию и централизованно управляет ресурсом.
Запуск Task { await resource.close() } внутри defer допустим как отдельное планирование работы, но не гарантирует её завершение до возврата из функции и не позволяет естественно объединить ошибку очистки с исходной ошибкой. Поэтому такой вариант подходит только когда очистка действительно может выполняться независимо.
Сервис открывает асинхронную транзакцию, выполняет несколько операций и обязан подтвердить её либо откатить до завершения метода. Использование defer удобно для синхронного освобождения памяти, но не подходит для асинхронных commit и rollback.
Рассматривались три варианта. Запускать закрытие через отдельную Task просто, но он не гарантирует порядок и завершение. Дублировать await commit и await rollback надёжно, однако увеличивает риск расхождения веток. Выбранной стала отдельная асинхронная обёртка управления транзакцией: она централизует успешное завершение, откат и правила обработки ошибок.
В результате вызывающий код получает подтверждение только после фактического завершения транзакции, а исходная ошибка операции не теряется из-за фоновой очистки.
Выполнится ли defer, если async-функция была отменена?
Да, если отмена привела к обычному выходу из области видимости. Сама отмена в Swift обычно является состоянием задачи и не прерывает произвольный код автоматически; функция должна проверить отмену или вызвать отменяемую операцию, которая сообщит о ней. После такого выхода зарегистрированный синхронный defer выполнится.
Можно ли вызвать обычный синхронный метод ресурса из defer внутри async-функции?
Да. Ограничение касается именно приостанавливающих операций. Синхронная очистка в defer допустима и сохраняет обычную гарантию выполнения при выходе из области.
Что произойдёт, если асинхронная очистка сама выбросит ошибку?
Её нужно обрабатывать явно. В ветке catch обычно сохраняют исходную ошибку, а ошибку очистки записывают в журнал или присоединяют к диагностическому контексту; без специального решения новая ошибка может скрыть первопричину. В успешной ветке ошибку очистки можно передать вызывающему коду как результат операции.