Unstructured Concurrency
Если SC - это явно упорядоченные отношения между родильской и дочерними задачами, то Unstructured Concurrency — полная противоположность.
Unstructured concurrency (UC), напротив, предоставляет разработчику больше гибкости: задачи могут существовать независимо друг от друга, не имеют явной иерархии и не связаны напрямую с родительской задачей. В определенных ситуациях не обойтись без UC и такое поведение также означает, что разработчик сам отвечает за корректное управление задачами, отмену и обработку ошибок.
important
Основное отличие Unstructured от Structured — отсуствие родительской задачи!
На примере
В самом простом сценарии (когда мы не сможем управлять задачей), нужно использовать структуру Task
или Task.detached
передавая в замыкание код:
Task {…}
Task.detached {…}
Более продвинутым вариантом послужит явно объявить тип данных (можно не указавать, компилятор определит тип данных сам) с последующим управлением:
- Отменой задачи
- Получение значения
- Получением результата
- Индикация отмены
isCancelled
let handleNameViaTask = Task<String, Never> {
"Дорогой читатель"
}
// 1. Отмена задачи
// handleNameViaTask.cancel()
// 2. Получение значения
// print(await handleNameViaTask.value)
/*
3. Обработка результата с помощью switch паттерна или guard case
switch await handleNameViaTask.result {
case let .success(userName): print("User name: ", userName)
case let .failure(error): print(error.localizedDescription)
}
guard case let .success(userName) = await handleNameViaTask.result else { return }
*/
// 4. Индикация отмены
// handleNameViaTask.isCancelled
Помимо этого, задача может быть объявлена в качестве опционального свойства:
struct Worker {
var task: Task<Void, Never>?
}
Время жизни задачи
Когда задача в методе execute
завершилась, замыкание оперативно освобождается. Удерживание задачи не приводит к постоянному циклу, потому что все ссылки, которые захвачены задачей, освобождаются после ее завершения. В результате задача редко нуждаются в захвате слабых ссылок.
В коде ниже нет необходимости захватывать актор как слабую ссылку, т.к. по завершении задачи, ссылка на актор будет освобождена, разрывая reference cycle между Task
и актором.
struct EmptyResult {}
actor Worker {
var workerTask: Task<Void, Never>?
var result: EmptyResult?
deinit {
assert(workerTask != nil)
print("Retain count = ", _getRetainCount(Worker.self))
print("Weak Retain count = ", _getWeakRetainCount(Worker.self))
print("deinit actor")
}
func execute() {
workerTask = Task {
print("начало работы задачи")
try? await Task.sleep(for: .seconds(1))
self.result = EmptyResult()
print("конец работы, выход из области видимости")
}
}
}
await Worker().execute()
note
Обратите внимание, что актор удерживается только за счет использования self
методом execute()
и метод execute()
выполняется сразу, не ожидая завершения неструктурированной задачи. Когда задача завершается и ее замыкание уничтожается, сильная ссылка на актор также освобождается, позволяя актору корректно деинициализироваться, как и полагается.
начало работы задачи
конец работы, выход из области видимости
Retain count = 1
Weak Retain count = 1
deinit actor