Трюки и полезное
В этой главе будут рассмотрены различные трюки и куски кода, которые можно использовать в различных ситуациях.
Hop to MainActor
Функция hopToMainActor(_:)
с помощью unsafeBitCast
обходит проверку компилятора на выполнение замыкания с @MainActor
на главном потоке, преобразуя его в обычное замыкание () -> ()
.
Это позволяет выполнить замыкание в любом потоке, нарушая акторную изоляцию и потенциально создавая проблемы с потокобезопасностью.
func hopToMainActor(_ x: @escaping @MainActor () -> ()) {
typealias Func = () -> ()
let x2 = unsafeBitCast(x, to: Func.self)
x2()
}
@MainActor
func another() async {
hopToMainActor { }
}
note
unsafeBitCast — это низкоуровневое преобразование типов, которое позволяет преобразовать один тип в другой без проверки их совместимости.
Сравнение приоритета задачи
Функция ниже служит для сравнения приоритета задачи:
func executedAt(priority: TaskPriority) async {
print("START 🔦:")
let currentPrioprity = Task.currentPriority
while (priority != currentPrioprity) {
print("Task priority = \(currentPrioprity) != fn \(priority)")
try? await Task.sleep(for: .seconds(1))
}
}
await executedAt(priority: .background)
Вывод
#![allow(unused)] fn main() { START 🔦: Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background Task priority = TaskPriority.high != fn TaskPriority.background … }
Функция executedAt(priority:)
проверяет текущий приоритет задачи через свойство Task.currentPriority
и сравнивает его (приоритет) с переданным значением priority
. Если они не совпадают, она выводит сообщение в консоль и засыпает на одну секунду, повторяя проверку до тех пор, пока приоритеты не станут равными. В данном примере функция ожидает, пока текущий приоритет задачи не станет равен .background
.
Скачивание данных
В примере кода функция download10MB
загружает данные по URL и выводит время выполнения задачи. В операторе defer
мы выводим время завершения работы задачи.
func download10MB(id: Int) async throws -> Data {
let readmeURL = URL(string: "https://raw.githubusercontent.com/wmorgue/wmorgue/main/README.md")!
let startDate = Date()
print("Task #\(id) started downloading.")
defer {
let duration = Date().timeIntervalSince(startDate)
print("Task #\(id) completed in \(duration) seconds.")
}
return try await URLSession.shared.data(from: readmeURL).0
}
for r in 0...10 {
Task {
try await download10MB(id: r)
}
}
Вывод
Task #1 started downloading.
Task #2 started downloading.
Task #7 started downloading.
Task #0 started downloading.
Task #4 started downloading.
Task #6 started downloading.
Task #5 started downloading.
Task #3 started downloading.
Task #10 started downloading.
Task #9 started downloading.
Task #8 started downloading.
Task #5 completed in 1.651481032371521 seconds.
Task #2 completed in 1.6585689783096313 seconds.
Task #3 completed in 1.6518199443817139 seconds.
Task #6 completed in 1.651737928390503 seconds.
Task #4 completed in 1.651810884475708 seconds.
Task #7 completed in 1.65175199508667 seconds.
Task #0 completed in 1.6590250730514526 seconds.
Task #1 completed in 1.658919095993042 seconds.
Task #10 completed in 1.5991131067276 seconds.
Task #8 completed in 1.5986690521240234 seconds.
Task #9 completed in 1.5986000299453735 seconds.
Но более правильным и оптимальным вариантом будет использование withThrowingTaskGroup
вместо цикла for
по нескольким причинам:
- Управление задачами:
withThrowingTaskGroup
позволяет эффективно управлять группой асинхронных задач. В отличие от использованияTask
в цикле, где задачи работают независимо друг от друга,TaskGroup
дает контроль над выполнением всех задач и их завершением, что упрощает управление асинхронностью. - Конкурентная обработка: Внутри
TaskGroup
задачи выполняются параллельно, и группа завершится только тогда, когда завершатся все задачи. Это предотвращает случайные ошибки, когда одна задача может завершиться раньше, чем другие, или если они не будут правильно синхронизированы. - Обработка ошибок:
withThrowingTaskGroup
встроенно обрабатывает ошибки. Если одна из задач выбросит исключение, выполнение всей группы завершится и управление будет передано обработчику ошибок. В цикле for без этой группы необходимо вручную следить за каждой задачей и обрабатывать ошибки индивидуально. - Управление ресурсами:
TaskGroup
использует встроенные механизмы для оптимизации использования ресурсов, предотвращая перегрузку системы созданием слишком большого количества параллельных задач, что делает его более эффективным. - Чистота и простота кода: С использованием
TaskGroup
код становится чище и проще для понимания, так как явным образом создается группа, в которой управляются все задачи, что повышает читаемость и сопровождаемость.
await withThrowingTaskGroup(of: Data.self) { group in
for r in 0...10 {
try await group.addTask { try await download10MB(id: r) }
}
}
Вывод
Task #3 started downloading.
Task #2 started downloading.
Task #1 started downloading.
Task #6 started downloading.
Task #5 started downloading.
Task #0 started downloading.
Task #4 started downloading.
Task #7 started downloading.
Task #10 started downloading.
Task #8 started downloading.
Task #9 started downloading.
Task #0 completed in 1.0400439500808716 seconds.
Task #6 completed in 1.0401649475097656 seconds.
Task #1 completed in 1.0402040481567383 seconds.
Task #8 completed in 1.0201719999313354 seconds.
Task #10 completed in 1.0204299688339233 seconds.
Task #9 completed in 1.0203959941864014 seconds.
Task #7 completed in 1.040037989616394 seconds.
Task #2 completed in 1.0401289463043213 seconds.
Task #3 completed in 1.0400739908218384 seconds.
Task #4 completed in 1.0400439500808716 seconds.
Task #5 completed in 1.0400769710540771 seconds.
Работа с приоритетами
В этом коде демонстрируется создание и выполнение асинхронных задач с разными приоритетами.
Сначала определяется массив taskPriorities
, содержащий приоритеты задач: .userInitiated
, .background
и .low
.
Функция makeEachTask
принимает приоритет задачи и асинхронную функцию, выводя сообщение о начале задачи с указанным приоритетом и выполняя переданную асинхронную функцию.
Внутри блока withTaskGroup
для каждого приоритета из массива создаются задачи, которые выполняются конкурентно.
let taskPriorities: [TaskPriority] = [.userInitiated, .background, .low]
func makeEachTask(with priority: TaskPriority, fn: () async -> Void) async {
print("Start task with \(priority) priority")
await fn()
}
await withTaskGroup(of: Void.self) { group in
for priority in taskPriorities {
await makeEachTask(with: priority) {
print("Finish task with \(priority.description) done")
}
}
}
Каждая задача выводит сообщение о своем завершении.
Вывод
Start task with TaskPriority.high priority
Finish task with TaskPriority.high done
Start task with TaskPriority.background priority
Finish task with TaskPriority.background done
Start task with TaskPriority.low priority
Finish task with TaskPriority.low done
Проверка отмены у группы
func test_detach_cancel_taskGroup() async {
print(#function) // CHECK: test_detach_cancel_taskGroup
await withTaskGroup(of: Void.self) { group in
group.cancelAll() // immediately cancel the group
print("group.cancel()") // CHECK: group.cancel()
group.addTask {
// immediately cancelled child task...
await withTaskCancellationHandler {
print("child: operation, was cancelled: \(Task.isCancelled)")
} onCancel: {
print("child: onCancel, was cancelled: \(Task.isCancelled)")
}
}
// CHECK: child: onCancel, was cancelled: true
// CHECK: child: operation, was cancelled: true
}
print("done") // CHECK: done
}
await test_detach_cancel_taskGroup()
Управление задачей при помощи Task.yield()
При использовании акторов иногда требуется явно приостановить выполнение задачи, чтобы обеспечить равномерное распределение ресурсов и дать другим задачам возможность выполняться.
Для этого используется метод Task.yield()
, который позволяет текущей задаче приостановиться и уступить выполнение другим задачам, ожидающим своей очереди.
protocol Start: Actor {
func start(times: Int) async -> Int
}
extension Start {
func start(times: Int) async -> Int {
for i in 0...times {
print("actor \(Self.self): \(#function) \(i)")
await Task.yield()
}
return times
}
}
actor One: Start {}
actor Two: Start {}
func yielding() async {
let one = One()
let two = Two()
await withTaskGroup(of: Int.self) { group in
group.addTask {
await one.start(times: 100)
}
group.addTask {
await two.start(times: 100)
}
}
}
await yielding()
Мы создаем протокол Start
, в котором определен метод start(times:)
, выполняющийся асинхронно в цикле.
В реализации метода (через расширение протокола) каждую итерацию вызывается метод Task.yield()
, что позволяет актору приостанавливать задачу и продолжать выполнение других задач.
В главной функции await yielding()
акторы One
и Two
запускаются параллельно в группе задач и каждая из них выполняет метод start(times:)
, приостанавливаясь в каждой итерации.
Такой подход полезен при выполнении ресурсоёмких операций, которые могут длиться долго.
Вставка Task.yield()
делает работу задач более сбалансированной, предотвращая блокировку других акторов и улучшая отзывчивость приложения.
Вывод
actor One: start(times:) 0
actor Two: start(times:) 0
actor One: start(times:) 1
actor Two: start(times:) 1
actor Two: start(times:) 2
actor One: start(times:) 2
actor One: start(times:) 3
actor Two: start(times:) 3
actor One: start(times:) 4
actor One: start(times:) 5
actor One: start(times:) 6
actor One: start(times:) 7
actor One: start(times:) 8
actor Two: start(times:) 4
actor Two: start(times:) 5
actor One: start(times:) 9
actor Two: start(times:) 6
actor Two: start(times:) 7
actor One: start(times:) 10
actor Two: start(times:) 8
actor One: start(times:) 11
actor One: start(times:) 12
actor One: start(times:) 13
actor Two: start(times:) 9
actor Two: start(times:) 10
actor One: start(times:) 14
actor Two: start(times:) 11
actor Two: start(times:) 12
actor Two: start(times:) 13
actor Two: start(times:) 14
actor Two: start(times:) 15
actor Two: start(times:) 16
actor One: start(times:) 15
actor One: start(times:) 16
actor Two: start(times:) 17
actor One: start(times:) 17
actor One: start(times:) 18
actor Two: start(times:) 18
actor One: start(times:) 19
actor Two: start(times:) 19
actor One: start(times:) 20
actor Two: start(times:) 20
actor One: start(times:) 21
actor Two: start(times:) 21
actor Two: start(times:) 22
actor One: start(times:) 22
actor Two: start(times:) 23
actor Two: start(times:) 24
actor One: start(times:) 23
actor One: start(times:) 24
actor Two: start(times:) 25
actor One: start(times:) 25
actor One: start(times:) 26
actor Two: start(times:) 26
actor One: start(times:) 27
actor One: start(times:) 28
actor One: start(times:) 29
actor One: start(times:) 30
actor One: start(times:) 31
actor One: start(times:) 32
actor One: start(times:) 33
actor Two: start(times:) 27
actor One: start(times:) 34
actor Two: start(times:) 28
actor One: start(times:) 35
actor Two: start(times:) 29
actor One: start(times:) 36
actor Two: start(times:) 30
actor One: start(times:) 37
actor Two: start(times:) 31
actor Two: start(times:) 32
actor Two: start(times:) 33
actor Two: start(times:) 34
actor One: start(times:) 38
actor Two: start(times:) 35
actor Two: start(times:) 36
actor One: start(times:) 39
actor Two: start(times:) 37
actor One: start(times:) 40
actor One: start(times:) 41
actor One: start(times:) 42
actor One: start(times:) 43
actor Two: start(times:) 38
actor One: start(times:) 44
actor Two: start(times:) 39
actor One: start(times:) 45
actor Two: start(times:) 40
actor One: start(times:) 46
actor Two: start(times:) 41
actor One: start(times:) 47
actor Two: start(times:) 42
actor Two: start(times:) 43
actor One: start(times:) 48
actor One: start(times:) 49
actor Two: start(times:) 44
actor One: start(times:) 50
actor Two: start(times:) 45
actor One: start(times:) 51
actor One: start(times:) 52
actor One: start(times:) 53
actor One: start(times:) 54
actor One: start(times:) 55
actor One: start(times:) 56
actor Two: start(times:) 46
actor Two: start(times:) 47
actor Two: start(times:) 48
actor Two: start(times:) 49
actor One: start(times:) 57
actor Two: start(times:) 50
actor One: start(times:) 58
actor Two: start(times:) 51
actor One: start(times:) 59
actor Two: start(times:) 52
actor One: start(times:) 60
actor Two: start(times:) 53
actor One: start(times:) 61
actor Two: start(times:) 54
actor One: start(times:) 62
actor Two: start(times:) 55
actor Two: start(times:) 56
actor One: start(times:) 63
actor Two: start(times:) 57
actor One: start(times:) 64
actor One: start(times:) 65
actor Two: start(times:) 58
actor One: start(times:) 66
actor One: start(times:) 67
actor One: start(times:) 68
actor Two: start(times:) 59
actor One: start(times:) 69
actor One: start(times:) 70
actor Two: start(times:) 60
actor One: start(times:) 71
actor One: start(times:) 72
actor One: start(times:) 73
actor One: start(times:) 74
actor Two: start(times:) 61
actor One: start(times:) 75
actor Two: start(times:) 62
actor One: start(times:) 76
actor One: start(times:) 77
actor Two: start(times:) 63
actor One: start(times:) 78
actor Two: start(times:) 64
actor One: start(times:) 79
actor Two: start(times:) 65
actor One: start(times:) 80
actor Two: start(times:) 66
actor One: start(times:) 81
actor Two: start(times:) 67
actor One: start(times:) 82
actor One: start(times:) 83
actor Two: start(times:) 68
actor Two: start(times:) 69
actor One: start(times:) 84
actor Two: start(times:) 70
actor One: start(times:) 85
actor One: start(times:) 86
actor Two: start(times:) 71
actor One: start(times:) 87
actor One: start(times:) 88
actor Two: start(times:) 72
actor One: start(times:) 89
actor Two: start(times:) 73
actor One: start(times:) 90
actor One: start(times:) 91
actor Two: start(times:) 74
actor One: start(times:) 92
actor Two: start(times:) 75
actor One: start(times:) 93
actor Two: start(times:) 76
actor One: start(times:) 94
actor Two: start(times:) 77
actor One: start(times:) 95
actor Two: start(times:) 78
actor One: start(times:) 96
actor One: start(times:) 97
actor One: start(times:) 98
actor Two: start(times:) 79
actor Two: start(times:) 80
actor One: start(times:) 99
actor Two: start(times:) 81
actor Two: start(times:) 82
actor Two: start(times:) 83
actor One: start(times:) 100
actor Two: start(times:) 84
actor Two: start(times:) 85
actor Two: start(times:) 86
actor Two: start(times:) 87
actor Two: start(times:) 88
actor Two: start(times:) 89
actor Two: start(times:) 90
actor Two: start(times:) 91
actor Two: start(times:) 92
actor Two: start(times:) 93
actor Two: start(times:) 94
actor Two: start(times:) 95
actor Two: start(times:) 96
actor Two: start(times:) 97
actor Two: start(times:) 98
actor Two: start(times:) 99
actor Two: start(times:) 100
Isolated deinit
@globalActor
final actor Moonland {
static let shared = Moonland()
}
@Moonland
func hello() {}
class MyClass {
@Moonland deinit {
hello()
}
}
MyClass()