Отсоединенные задачи позволяют создать новую задачу верхнего уровня и отключиться от текущего структурированного контекста параллелизма. Вы можете возразить, что их использование приводит к неструктурированному параллелизму, поскольку вы отключаете потенциально важные задачи.
Хотя отключение от структурированного параллелизма звучит ужасно, все же есть примеры использования, в которых вы можете извлечь выгоду из отсоединенных задач. Тем не менее, важно знать о последствиях, чтобы убедиться, что вы знаете, что делаете. Прежде чем читать эту статью, я рекомендую вам прочитать мои статьи о задачах и группах задач.
Что такое отдельная задача?
Отсоединенная задача выполняет данную операцию асинхронно как часть новой задачи верхнего уровня.
Task.detached(priority: .background) {
// Runs asynchronously
}
Код внутри замыкания будет выполняться асинхронно из родительского контекста.
Следующий код демонстрирует эту концепцию, используя асинхронный метод для вывода значения:
await asyncPrint("Operation one")
Task.detached(priority: .background) {
// Runs asynchronously
await self.asyncPrint("Operation two")
}
await asyncPrint("Operation three")
func asyncPrint(_ string: String) async {
print(string)
}
// Prints:
// Operation one
// Operation three
// Operation two
Другими словами, используя отсоединенную задачу, мы отошли от структурированного параллелизма и больше не контролируем порядок выполнения.
Риски использования отдельных задач
Задачи, которые выполняются отдельно, создают новый контекст для работы. Они не наследуют приоритет родительской задачи и локальное хранилище задачи, а также не отменяются, если родительская задача отменяется:
let outerTask = Task {
/// This one will cancel.
await longRunningAsyncOperation()
/// This detached task won't cancel.
Task.detached(priority: .background) {
/// And, therefore, this task won't cancel either.
await self.longRunningAsyncOperation()
}
}
outerTask.cancel()
Если вы хотите, чтобы отсоединенная задача была беспрепятственно отменена, вы должны сохранить ссылку и отменить ее вручную. Кроме того, они не отменяются автоматически, как только вы освобождаете ссылку. У вас больше не будет возможности отменить задачу самостоятельно, пока задача продолжается независимо.
Наконец, поскольку вы выполняете код асинхронно, вам придется использовать «я», чтобы явно указать семантику захвата:
Еще одним признаком риска, связанного с отключением от родительского локального хранилища, является то, что вы с большей вероятностью столкнетесь с циклами сохранения.
Когда использовать отсоединенную задачу
Отдельные задачи должны быть вашим последним средством. Во многих случаях вы сможете выполнять задачи параллельно, используя вместо этого группы задач, и получать выгоду от отношений родитель-потомок. Последнее позволит вам автоматически отменить родительскую задачу и все связанные с ней дочерние задачи.
Однако в некоторых случаях у вас есть операции, которые могут выполняться независимо, не требуют подключения к родительскому контексту и могут успешно завершиться в случае отмены родительской операции. Вы не хотите ждать результатов или блокировать родительский актор от выполнения других задач. Примером может быть очистка каталога:
Task.detached(priority: .background) {
await DirectoryCleaner.cleanup()
}
В этом примере мы не ссылаемся ни на какие локальные ссылки, используя self. Код очистки запускается независимо, может продолжаться, пока родительский контекст отменяется, и выполняется с использованием фонового приоритета.