TaskUtilities 是一个小型类型集合,可帮助排查异步 Swift 代码问题,并使同步代码可以安全地从异步上下文调用。
重要提示
RecursiveTaskLock
和 LockedValue
仅适用于必须使同步代码可以安全地从多个异步任务调用的不常见情况。请谨慎使用这些类,因为它们是阻塞的,并且容易出现死锁,例如 哲学家就餐问题。如果可以选择编写异步代码,Swift Actor 是保护可变状态的更好方法。
RecursiveTaskLock
提供了一个锁,可以从单个任务中递归锁定。当锁已被另一个任务锁定时,尝试从第二个任务获取锁将导致第二个任务阻塞,直到锁被释放。这使您可以在无法使用 Swift Actor 的情况下,使同步代码可以安全地从异步代码调用。
class Names {
let lock = RecursiveTaskLock()
var names: [String] = []
func contains(name: String) -> Bool {
lock.withLock {
names.contains(name)
}
}
func add(name: String) {
lock.withLock {
names.append(name)
}
}
}
在上面的示例中,NSLock
也能正常工作。但是在更复杂的情况下,如果可能存在嵌套锁,则需要递归锁。例如,如果 add
调用了 contains
,或者 add
调用了可能调用 contains
的外部闭包,则需要递归锁。
NSRecursiveLock
锁定一个线程,但 Swift 任务可以在线程之间移动。这使得 NSRecursiveLock
不安全,因为
RecursiveTaskLock
将操作锁定到一个任务,而不是一个线程。
LockedValue
将锁包装在可变数据周围,以便一次只能从一个任务访问它。这比直接使用 RecursiveTaskLock
更可取,因为它可以防止您在不锁定锁的情况下意外访问数据。
class Names {
let names = LockedValue<[String]>([])
func contains(name: String) -> Bool {
names.withLockedValue { names in
names.contains(name)
}
}
func add(name: String) {
names.withLockedValue { names in
names.append(name)
}
}
}
TaskPath
是一种调试辅助工具,可帮助您了解异步代码的任务结构。它允许您为任务命名,并从代码中的任意点检索该名称。
Task.detached {
TaskPath.with(name: "Fetch image") {
...
}
}
// In other code called from that task:
print(TaskPath.current) // Prints "{Task Fetch image}"
如果在代码中的不同点命名相同的任务,则调用结构将被保留
Task.detached {
TaskPath.with(name: "Fetch image") {
...
TaskPath.with(name: "Constructing request") {
...
}
}
}
// In other code called from the inner task:
print(TaskPath.current) // Prints "{Task Fetch image > Constructing request}"
将包 https://github.com/samalone/task-utilities
添加到您的 Xcode 项目中,或添加
.package(url: "https://github.com/samalone/task-utilities.git", from: "1.0.0"),
到您的 Package.swift
文件中的包依赖项。然后添加
.product(name: "TaskUtilities", package: "task-utilities"),
到您的包目标的 target dependencies 中。