samalone/task-utilities
TaskUtilities is a small collection of types that help troubleshoot asynchronous
RecursiveTaskLock
RecursiveTaskLock provides a lock that can be locked recursively from within a single task. Attempting to obtain the lock from a second task when it has been locked by another will cause the second task to block until the lock is released. This lets you make synchronous code safe to call from asynchronous code in cases where you cannot use Swift actors.
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)
}
}
}Why not NSLock?
In the example above an NSLock would also work fine. But in more complex cases where there could be nested locks, a recursive lock is needed. For instance if add called contains, or add called an external closure that might call contains, a recursive lock is needed.
Why not NSRecursiveLock?
NSRecursiveLock locks a thread, but Swift Tasks can move from thread to thread. That makes NSRecursiveLock unsafe because:
- two tasks running on the same thread could share a lock
- the same task running on a different thread might block waiting for the lock
RecursiveTaskLock locks the operation to one task, not one thread.
LockedValue
LockedValue wraps a lock around mutable data so that it can only be accessed from one Task at a time. This is preferable to using a RecursiveTaskLock directly, because it prevents you from accidentally accessing the data without locking the lock.
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
TaskPath is a debugging aid to help you understand the Task structure of asynchronous code. It allows you to give a task a name, and retrieve that name from an arbitrary point in your code.
Task.detached {
TaskPath.with(name: "Fetch image") {
...
}
}
// In other code called from that task:
print(TaskPath.current) // Prints "{Task Fetch image}"If you name the same Task at different points in your code, the call structure will be preserved:
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}"Installation
Add the package https://github.com/samalone/task-utilities to your Xcode project, or add:
.package(url: "https://github.com/samalone/task-utilities.git", from: "1.0.0"),to your package dependencies in your Package.swift file. Then add:
.product(name: "TaskUtilities", package: "task-utilities"),to the target dependencies of your package target.
Package Metadata
Repository: samalone/task-utilities
Default branch: main
README: README.md