Class_Concurrency5.9.0
TaskLocal
Property wrapper that defines a task-local value key.
- iOS
- 13.0+
- macOS
- 10.15+
- tvOS
- 13.0+
- watchOS
- 6.0+
@propertyWrapper final class TaskLocal<Value> where Value : Sendable
A task-local value is a value that can be bound and read in the context of a Task
. It is implicitly carried with the task, and is accessible by any child tasks the task creates (such as TaskGroup or async let
created tasks).
Task-local declarations
Task locals must be declared as static properties (or global properties, once property wrappers support these), like this:
enum TracingExample {
@TaskLocal
static let traceID: TraceID?
}
Default values
Task local values of optional types default to nil
. It is possible to define not-optional task-local values, and an explicit default value must then be defined instead.
The default value is returned whenever the task-local is read from a context which either: has no task available to read the value from (e.g. a synchronous function, called without any asynchronous function in its call stack), or no value was bound within the scope of the current task or any of its parent tasks.
Reading task-local values
Reading task local values is simple and looks the same as-if reading a normal static property:
guard let traceID = TracingExample.traceID else {
print("no trace id")
return
}
print(traceID)
It is possible to perform task-local value reads from either asynchronous or synchronous functions. Within asynchronous functions, as a “current” task is always guaranteed to exist, this will perform the lookup in the task local context.
A lookup made from the context of a synchronous function, that is not called from an asynchronous function (!), will immediately return the task-local’s default value.
Binding task-local values
Task local values cannot be set
directly and must instead be bound using the scoped $traceID.withValue() { ... }
operation. The value is only bound for the duration of that scope, and is available to any child tasks which are created within that scope.
Detached tasks do not inherit task-local values, however tasks created using the Task { ... }
initializer do inherit task-locals by copying them to the new asynchronous task, even though it is an un-structured task.
Examples
@TaskLocal
static var traceID: TraceID?
print("traceID: \(traceID)") // traceID: nil
$traceID.withValue(1234) { // bind the value
print("traceID: \(traceID)") // traceID: 1234
call() // traceID: 1234
Task { // unstructured tasks do inherit task locals by copying
call() // traceID: 1234
}
Task.detached { // detached tasks do not inherit task-local values
call() // traceID: nil
}
}
func call() {
print("traceID: \(traceID)") // 1234
}
This type must be a class
so it has a stable identity, that is used as key value for lookups in the task local storage.
Citizens in _Concurrency
Conformances
protocol CustomStringConvertible
A type with a customized textual representation.
protocol Sendable
A type whose values can safely be passed across concurrency domains by copying.
Members
init(wrappedValue: Value
) var description: String
var projectedValue: TaskLocal<Value>
var wrappedValue: Value
func get(
) -> Value Gets the value currently bound to this task-local from the current task.
func withValue<R>(Value, operation: () throws -> R, file: String, line: UInt
) rethrows -> R Binds the task-local to the specific value for the duration of the synchronous operation.
func withValue<R>(Value, operation: () async throws -> R, file: String, line: UInt
) async rethrows -> R Binds the task-local to the specific value for the duration of the asynchronous operation.