@@ -24,16 +24,22 @@ import Swift
24
24
/// Task locals must be declared as static properties (or global properties,
25
25
/// once property wrappers support these), like this:
26
26
///
27
- /// enum TracingExample {
27
+ /// enum Example {
28
28
/// @TaskLocal
29
29
/// static let traceID: TraceID?
30
30
/// }
31
31
///
32
32
/// ### Default values
33
- /// Task local values of optional types default to `nil`. It is possible to define
34
- /// not-optional task-local values, and an explicit default value must then be
35
- /// defined instead.
33
+ /// Reading a task local value when no value was bound to it results in returning
34
+ /// its default value. For a task local declared as optional (such as e.g. `TraceID?`),
35
+ /// this defaults to nil, however a different default value may be defined at declaration
36
+ /// site of the task local, like this:
36
37
///
38
+ /// enum Example {
39
+ /// @TaskLocal
40
+ /// static let traceID: TraceID = TraceID.default
41
+ /// }
42
+ ///
37
43
/// The default value is returned whenever the task-local is read
38
44
/// from a context which either: has no task available to read the value from
39
45
/// (e.g. a synchronous function, called without any asynchronous function in its call stack),
@@ -43,19 +49,14 @@ import Swift
43
49
/// Reading task local values is simple and looks the same as-if reading a normal
44
50
/// static property:
45
51
///
46
- /// guard let traceID = TracingExample .traceID else {
52
+ /// guard let traceID = Example .traceID else {
47
53
/// print("no trace id")
48
54
/// return
49
55
/// }
50
56
/// print(traceID)
51
57
///
52
58
/// It is possible to perform task-local value reads from either asynchronous
53
- /// or synchronous functions. Within asynchronous functions, as a "current" task
54
- /// is always guaranteed to exist, this will perform the lookup in the task local context.
55
- ///
56
- /// A lookup made from the context of a synchronous function, that is not called
57
- /// from an asynchronous function (!), will immediately return the task-local's
58
- /// default value.
59
+ /// or synchronous functions.
59
60
///
60
61
/// ### Binding task-local values
61
62
/// Task local values cannot be `set` directly and must instead be bound using
@@ -67,29 +68,75 @@ import Swift
67
68
/// the `Task { ... }` initializer do inherit task-locals by copying them to the
68
69
/// new asynchronous task, even though it is an un-structured task.
69
70
///
71
+ /// ### Using task local values outside of tasks
72
+ /// It is possible to bind and read task local values outside of tasks.
73
+ ///
74
+ /// This comes in handy within synchronous functions which are not guaranteed
75
+ /// to be called from within a task. When binding a task-local value from
76
+ /// outside of a task, the runtime will set a thread-local in which the same
77
+ /// storage mechanism as used within tasks will be used. This means that you
78
+ /// can reliably bind and read task local values without having to worry
79
+ /// about the specific calling context, e.g.:
80
+ ///
81
+ /// func enter() {
82
+ /// Example.$traceID.withValue("1234") {
83
+ /// read() // always "1234", regardless if enter() was called from inside a task or not:
84
+ /// }
85
+ ///
86
+ /// func read() -> String {
87
+ /// if let value = Self.traceID {
88
+ /// "\(value)"
89
+ /// } else {
90
+ /// "<no value>"
91
+ /// }
92
+ /// }
93
+ ///
94
+ /// // 1) Call `enter` from non-Task code
95
+ /// // e.g. synchronous main() or non-Task thread (e.g. a plain pthread)
96
+ /// enter()
97
+ ///
98
+ /// // 2) Call 'enter' from Task
99
+ /// Task {
100
+ /// enter()
101
+ /// }
102
+ ///
103
+ /// In either cases listed above, the binding and reading of the task-local value works as expected.
104
+ ///
70
105
/// ### Examples
71
106
///
72
- /// @TaskLocal
73
- /// static var traceID: TraceID?
74
107
///
75
- /// print("traceID: \(traceID)") // traceID: nil
108
+ /// enum Example {
109
+ /// @TaskLocal
110
+ /// static var traceID: TraceID?
111
+ /// }
112
+ ///
113
+ /// func read() -> String {
114
+ /// if let value = Self.traceID {
115
+ /// "\(value)"
116
+ /// } else {
117
+ /// "<no value>"
118
+ /// }
119
+ /// }
120
+ ///
121
+ /// await Example.$traceID.withValue(1234) { // bind the value
122
+ /// print("traceID: \(Example.traceID)") // traceID: 1234
123
+ /// read() // traceID: 1234
124
+ ///
125
+ /// async let id = read() // async let child task, traceID: 1234
76
126
///
77
- /// $traceID.withValue(1234) { // bind the value
78
- /// print("traceID: \(traceID)") // traceID: 1234
79
- /// call() // traceID: 1234
127
+ /// await withTaskGroup(of: String.self) { group in
128
+ /// group.addTask { read() } // task group child task, traceID: 1234
129
+ /// return await group.next()!
130
+ /// }
80
131
///
81
132
/// Task { // unstructured tasks do inherit task locals by copying
82
- /// call () // traceID: 1234
133
+ /// read () // traceID: 1234
83
134
/// }
84
135
///
85
136
/// Task.detached { // detached tasks do not inherit task-local values
86
- /// call () // traceID: nil
137
+ /// read () // traceID: nil
87
138
/// }
88
139
/// }
89
- ///
90
- /// func call() {
91
- /// print("traceID: \(traceID)") // 1234
92
- /// }
93
140
@propertyWrapper
94
141
@available ( SwiftStdlib 5 . 1 , * )
95
142
public final class TaskLocal < Value: Sendable > : Sendable , CustomStringConvertible {
0 commit comments