Skip to content

Commit 9d3540f

Browse files
authored
[docs] TaskLocal APIs from non-Task code (#70622)
1 parent b5d1ecc commit 9d3540f

File tree

1 file changed

+70
-23
lines changed

1 file changed

+70
-23
lines changed

stdlib/public/Concurrency/TaskLocal.swift

Lines changed: 70 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,22 @@ import Swift
2424
/// Task locals must be declared as static properties (or global properties,
2525
/// once property wrappers support these), like this:
2626
///
27-
/// enum TracingExample {
27+
/// enum Example {
2828
/// @TaskLocal
2929
/// static let traceID: TraceID?
3030
/// }
3131
///
3232
/// ### 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:
3637
///
38+
/// enum Example {
39+
/// @TaskLocal
40+
/// static let traceID: TraceID = TraceID.default
41+
/// }
42+
///
3743
/// The default value is returned whenever the task-local is read
3844
/// from a context which either: has no task available to read the value from
3945
/// (e.g. a synchronous function, called without any asynchronous function in its call stack),
@@ -43,19 +49,14 @@ import Swift
4349
/// Reading task local values is simple and looks the same as-if reading a normal
4450
/// static property:
4551
///
46-
/// guard let traceID = TracingExample.traceID else {
52+
/// guard let traceID = Example.traceID else {
4753
/// print("no trace id")
4854
/// return
4955
/// }
5056
/// print(traceID)
5157
///
5258
/// 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.
5960
///
6061
/// ### Binding task-local values
6162
/// Task local values cannot be `set` directly and must instead be bound using
@@ -67,29 +68,75 @@ import Swift
6768
/// the `Task { ... }` initializer do inherit task-locals by copying them to the
6869
/// new asynchronous task, even though it is an un-structured task.
6970
///
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+
///
70105
/// ### Examples
71106
///
72-
/// @TaskLocal
73-
/// static var traceID: TraceID?
74107
///
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
76126
///
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+
/// }
80131
///
81132
/// Task { // unstructured tasks do inherit task locals by copying
82-
/// call() // traceID: 1234
133+
/// read() // traceID: 1234
83134
/// }
84135
///
85136
/// Task.detached { // detached tasks do not inherit task-local values
86-
/// call() // traceID: nil
137+
/// read() // traceID: nil
87138
/// }
88139
/// }
89-
///
90-
/// func call() {
91-
/// print("traceID: \(traceID)") // 1234
92-
/// }
93140
@propertyWrapper
94141
@available(SwiftStdlib 5.1, *)
95142
public final class TaskLocal<Value: Sendable>: Sendable, CustomStringConvertible {

0 commit comments

Comments
 (0)