Skip to content

Commit 38a951f

Browse files
committed
make TaskExecutor params not optional; add DefaultConcurrentExecutor
1 parent 9d3540f commit 38a951f

File tree

4 files changed

+115
-78
lines changed

4 files changed

+115
-78
lines changed

stdlib/public/Concurrency/Task+TaskExecutor.swift

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,50 @@ import Swift
2929
/// Passing `nil` as executor means disabling any preference preference (if it was set) and the task hierarchy
3030
/// will execute without any executor preference until a different preference is set.
3131
///
32+
/// ### Example
33+
///
34+
/// Task {
35+
/// // case 0) "no task executor preference"
36+
///
37+
/// // default task executor
38+
/// // ...
39+
/// await SomeDefaultActor().hello() // default executor
40+
/// await ActorWithCustomExecutor().hello() // 'hello' executes on actor's custom executor
41+
///
42+
/// // child tasks execute on default executor:
43+
/// async let x = ...
44+
/// await withTaskGroup(of: Int.self) { group in g.addTask { 7 } }
45+
///
46+
/// await withTaskExecutor(specific) {
47+
/// // case 1) 'specific' task executor preference
48+
///
49+
/// // 'specific' task executor
50+
/// // ...
51+
/// await SomeDefaultActor().hello() // 'hello' executes on 'specific' executor
52+
/// await ActorWithCustomExecutor().hello() // 'hello' executes on actor's custom executor (same as case 0)
53+
///
54+
/// // child tasks execute on 'specific' task executor:
55+
/// async let x = ...
56+
/// await withTaskGroup(of: Int.self) { group in g.addTask { 7 } }
57+
///
58+
/// // disable the task executor preference:
59+
/// await withTaskExecutor(.default) {
60+
/// // equivalent to case 0) preference is "default"
61+
///
62+
/// // default task executor
63+
/// // ...
64+
/// await SomeDefaultActor().hello() // default executor (same as case 0)
65+
/// await ActorWithCustomExecutor().hello() // 'hello' executes on actor's custom executor (same as case 0)
66+
///
67+
/// // child tasks execute on default executor (same as case 0):
68+
/// async let x = ...
69+
/// await withTaskGroup(of: Int.self) { group in g.addTask { 7 } }
70+
/// }
71+
/// }
72+
/// }
73+
///
3274
/// - Parameters:
33-
/// - executor: the task executor to use as preferred task executor; if `nil` it is interpreted as "no preference"
75+
/// - executorPreference: the task executor to use as preferred task executor; if `nil` it is interpreted as "no preference"
3476
/// - operation: the operation to execute on the passed executor; if the executor was `nil`, this will execute on the default global concurrent executor.
3577
/// - Returns: the value returned from the `operation` closure
3678
/// - Throws: if the operation closure throws
@@ -39,22 +81,11 @@ import Swift
3981
@available(SwiftStdlib 9999, *)
4082
@_unsafeInheritExecutor // calling withTaskExecutor MUST NOT perform the "usual" hop to global
4183
public func _withTaskExecutor<T: Sendable>(
42-
_ executor: (any _TaskExecutor)?,
84+
_ taskExecutorPreference: any _TaskExecutor,
4385
operation: @Sendable () async throws -> T
4486
) async rethrows -> T {
4587
let taskExecutorBuiltin: Builtin.Executor =
46-
if let executor {
47-
// We need to go through the asUnowned... for serial executors,
48-
// because they encode certain behavior in the reference bits,
49-
// so we cannot just cast and assume it'll be correct.
50-
executor.asUnownedTaskExecutor().executor
51-
} else {
52-
// we must push a "no preference" record onto the task
53-
// because there may be other records issuing a preference already,
54-
// so by pushing this "no preference" (undefined task executor),
55-
// we turn off the task executor preference for the scope of `operation`.
56-
_getUndefinedTaskExecutor()
57-
}
88+
taskExecutorPreference.asUnownedTaskExecutor().executor
5889

5990
let record = _pushTaskExecutorPreference(taskExecutorBuiltin)
6091
defer {
@@ -67,7 +98,45 @@ public func _withTaskExecutor<T: Sendable>(
6798
return try await operation()
6899
}
69100

70-
/// Task with specified executor ----------------------------------------------
101+
/// Default global concurrent pool TaskExecutor --------------------------------
102+
103+
@available(SwiftStdlib 9999, *)
104+
extension _TaskExecutor where Self == DefaultConcurrentExecutor {
105+
106+
/// The default executor preference, setting it using ``withTaskExecutor(_:)``
107+
/// is equivalent to disabling an existing task executor preference, or
108+
/// stating that task now has "no task executor preference".
109+
@available(SwiftStdlib 9999, *)
110+
public static var `default`: DefaultConcurrentExecutor {
111+
DefaultConcurrentExecutor.shared
112+
}
113+
114+
}
115+
116+
/// A task executor which enqueues all work on the default global concurrent
117+
/// thread pool that is used as the default executor for Swift concurrency
118+
/// tasks.
119+
@available(SwiftStdlib 9999, *)
120+
public final class DefaultConcurrentExecutor: _TaskExecutor {
121+
public static let shared: DefaultConcurrentExecutor = .init()
122+
123+
private init() {}
124+
125+
public func enqueue(_ job: consuming ExecutorJob) {
126+
_enqueueJobGlobal(job.context)
127+
}
128+
129+
public func asUnownedTaskExecutor() {
130+
// The "default global concurrent executor" is simply the "undefined" one.
131+
// We represent it as the `(0, 0)` ExecutorRef and it is handled properly
132+
// by the runtime, without having to call through to the
133+
// `DefaultConcurrentExecutor` declared in Swift.
134+
UnownedTaskExecutor(_getUndefinedTaskExecutor())
135+
}
136+
137+
}
138+
139+
/// Task with specified executor -----------------------------------------------
71140

72141
@available(SwiftStdlib 9999, *)
73142
extension Task where Failure == Never {

stdlib/public/Concurrency/TaskGroup+TaskExecutor.swift

Lines changed: 24 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ extension TaskGroup {
2222
/// Adds a child task to the group and enqueue it on the specified executor.
2323
///
2424
/// - Parameters:
25-
/// - taskExecutor: The task executor that the child task should be started on and keep using.
25+
/// - taskExecutorPreference: The task executor that the child task should be started on and keep using.
2626
/// If `nil` is passed explicitly, tht parent task's executor preference (if any),
2727
/// will be ignored. In order to inherit the parent task's executor preference
2828
/// invoke `_addTask()` without passing a value to the `taskExecutor` parameter,
@@ -33,7 +33,7 @@ extension TaskGroup {
3333
/// - operation: The operation to execute as part of the task group.
3434
@_alwaysEmitIntoClient
3535
public mutating func _addTask(
36-
on taskExecutor: (any _TaskExecutor)?,
36+
on taskExecutorPreference: any _TaskExecutor,
3737
priority: TaskPriority? = nil,
3838
operation: __owned @Sendable @escaping () async -> ChildTaskResult
3939
) {
@@ -45,11 +45,7 @@ extension TaskGroup {
4545
isDiscardingTask: false)
4646

4747
let executorBuiltin: Builtin.Executor =
48-
if let taskExecutor {
49-
taskExecutor.asUnownedTaskExecutor().executor
50-
} else {
51-
_getUndefinedTaskExecutor()
52-
}
48+
taskExecutorPreference.asUnownedTaskExecutor().executor
5349

5450
// Create the task in this group with an executor preference.
5551
_ = Builtin.createAsyncTaskInGroupWithExecutor(flags, _group, executorBuiltin, operation)
@@ -61,7 +57,7 @@ extension TaskGroup {
6157
/// Adds a child task to the group and enqueue it on the specified executor, unless the group has been canceled.
6258
///
6359
/// - Parameters:
64-
/// - taskExecutor: The task executor that the child task should be started on and keep using.
60+
/// - taskExecutorPreference: The task executor that the child task should be started on and keep using.
6561
/// If `nil` is passed explicitly, tht parent task's executor preference (if any),
6662
/// will be ignored. In order to inherit the parent task's executor preference
6763
/// invoke `_addTaskUnlessCancelled()` without passing a value to the `taskExecutor` parameter,
@@ -74,7 +70,7 @@ extension TaskGroup {
7470
/// otherwise `false`.
7571
@_alwaysEmitIntoClient
7672
public mutating func _addTaskUnlessCancelled(
77-
on taskExecutor: (any _TaskExecutor)?,
73+
on taskExecutorPreference: any _TaskExecutor,
7874
priority: TaskPriority? = nil,
7975
operation: __owned @Sendable @escaping () async -> ChildTaskResult
8076
) -> Bool {
@@ -92,11 +88,7 @@ extension TaskGroup {
9288
isDiscardingTask: false)
9389

9490
let executorBuiltin: Builtin.Executor =
95-
if let taskExecutor {
96-
taskExecutor.asUnownedTaskExecutor().executor
97-
} else {
98-
_getUndefinedTaskExecutor()
99-
}
91+
taskExecutorPreference.asUnownedTaskExecutor().executor
10092

10193
// Create the task in this group with an executor preference.
10294
_ = Builtin.createAsyncTaskInGroupWithExecutor(flags, _group, executorBuiltin, operation)
@@ -115,7 +107,7 @@ extension ThrowingTaskGroup {
115107
/// Adds a child task to the group and enqueue it on the specified executor.
116108
///
117109
/// - Parameters:
118-
/// - taskExecutor: The task executor that the child task should be started on and keep using.
110+
/// - taskExecutorPreference: The task executor that the child task should be started on and keep using.
119111
/// If `nil` is passed explicitly, tht parent task's executor preference (if any),
120112
/// will be ignored. In order to inherit the parent task's executor preference
121113
/// invoke `_addTask()` without passing a value to the `taskExecutor` parameter,
@@ -126,7 +118,7 @@ extension ThrowingTaskGroup {
126118
/// - operation: The operation to execute as part of the task group.
127119
@_alwaysEmitIntoClient
128120
public mutating func _addTask(
129-
on taskExecutor: (any _TaskExecutor)?,
121+
on taskExecutorPreference: any _TaskExecutor,
130122
priority: TaskPriority? = nil,
131123
operation: __owned @Sendable @escaping () async throws -> ChildTaskResult
132124
) {
@@ -138,11 +130,7 @@ extension ThrowingTaskGroup {
138130
isDiscardingTask: false)
139131

140132
let executorBuiltin: Builtin.Executor =
141-
if let taskExecutor {
142-
taskExecutor.asUnownedTaskExecutor().executor
143-
} else {
144-
_getUndefinedTaskExecutor()
145-
}
133+
taskExecutorPreference.asUnownedTaskExecutor().executor
146134

147135
// Create the task in this group with an executor preference.
148136
_ = Builtin.createAsyncTaskInGroupWithExecutor(flags, _group, executorBuiltin, operation)
@@ -154,7 +142,7 @@ extension ThrowingTaskGroup {
154142
/// Adds a child task to the group and enqueue it on the specified executor, unless the group has been canceled.
155143
///
156144
/// - Parameters:
157-
/// - taskExecutor: The task executor that the child task should be started on and keep using.
145+
/// - taskExecutorPreference: The task executor that the child task should be started on and keep using.
158146
/// If `nil` is passed explicitly, tht parent task's executor preference (if any),
159147
/// will be ignored. In order to inherit the parent task's executor preference
160148
/// invoke `_addTaskUnlessCancelled()` without passing a value to the `taskExecutor` parameter,
@@ -167,7 +155,7 @@ extension ThrowingTaskGroup {
167155
/// otherwise `false`.
168156
@_alwaysEmitIntoClient
169157
public mutating func _addTaskUnlessCancelled(
170-
on taskExecutor: (any _TaskExecutor)?,
158+
on taskExecutorPreference: any _TaskExecutor,
171159
priority: TaskPriority? = nil,
172160
operation: __owned @Sendable @escaping () async throws -> ChildTaskResult
173161
) -> Bool {
@@ -185,11 +173,7 @@ extension ThrowingTaskGroup {
185173
isDiscardingTask: false)
186174

187175
let executorBuiltin: Builtin.Executor =
188-
if let taskExecutor {
189-
taskExecutor.asUnownedTaskExecutor().executor
190-
} else {
191-
_getUndefinedTaskExecutor()
192-
}
176+
taskExecutorPreference.asUnownedTaskExecutor().executor
193177

194178
// Create the task in this group with an executor preference.
195179
_ = Builtin.createAsyncTaskInGroupWithExecutor(flags, _group, executorBuiltin, operation)
@@ -208,7 +192,7 @@ extension DiscardingTaskGroup {
208192
/// Adds a child task to the group and enqueue it on the specified executor.
209193
///
210194
/// - Parameters:
211-
/// - taskExecutor: The task executor that the child task should be started on and keep using.
195+
/// - taskExecutorPreference: The task executor that the child task should be started on and keep using.
212196
/// If `nil` is passed explicitly, tht parent task's executor preference (if any),
213197
/// will be ignored. In order to inherit the parent task's executor preference
214198
/// invoke `_addTask()` without passing a value to the `taskExecutor` parameter,
@@ -219,7 +203,7 @@ extension DiscardingTaskGroup {
219203
/// - operation: The operation to execute as part of the task group.
220204
@_alwaysEmitIntoClient
221205
public mutating func _addTask(
222-
on taskExecutor: (any _TaskExecutor)?,
206+
on taskExecutorPreference: any _TaskExecutor,
223207
priority: TaskPriority? = nil,
224208
operation: __owned @Sendable @escaping () async throws -> Void
225209
) {
@@ -231,11 +215,7 @@ extension DiscardingTaskGroup {
231215
isDiscardingTask: true)
232216

233217
let executorBuiltin: Builtin.Executor =
234-
if let taskExecutor {
235-
taskExecutor.asUnownedTaskExecutor().executor
236-
} else {
237-
_getUndefinedTaskExecutor()
238-
}
218+
taskExecutorPreference.asUnownedTaskExecutor().executor
239219

240220
// Create the task in this group with an executor preference.
241221
_ = Builtin.createAsyncDiscardingTaskInGroupWithExecutor(flags, _group, executorBuiltin, operation)
@@ -248,7 +228,7 @@ extension DiscardingTaskGroup {
248228
/// unless the group has been canceled.
249229
///
250230
/// - Parameters:
251-
/// - taskExecutor: The task executor that the child task should be started on and keep using.
231+
/// - taskExecutorPreference: The task executor that the child task should be started on and keep using.
252232
/// If `nil` is passed explicitly, tht parent task's executor preference (if any),
253233
/// will be ignored. In order to inherit the parent task's executor preference
254234
/// invoke `_addTask()` without passing a value to the `taskExecutor` parameter,
@@ -261,7 +241,7 @@ extension DiscardingTaskGroup {
261241
/// otherwise `false`.
262242
@_alwaysEmitIntoClient
263243
public mutating func _addTaskUnlessCancelled(
264-
on taskExecutor: (any _TaskExecutor)?,
244+
on taskExecutorPreference: any _TaskExecutor,
265245
priority: TaskPriority? = nil,
266246
operation: __owned @Sendable @escaping () async -> Void
267247
) -> Bool {
@@ -279,11 +259,7 @@ extension DiscardingTaskGroup {
279259
)
280260

281261
let executorBuiltin: Builtin.Executor =
282-
if let taskExecutor {
283-
taskExecutor.asUnownedTaskExecutor().executor
284-
} else {
285-
_getUndefinedTaskExecutor()
286-
}
262+
taskExecutorPreference.asUnownedTaskExecutor().executor
287263

288264
// Create the task in this group with an executor preference.
289265
_ = Builtin.createAsyncDiscardingTaskInGroupWithExecutor(flags, _group, executorBuiltin, operation)
@@ -302,7 +278,7 @@ extension ThrowingDiscardingTaskGroup {
302278
/// Adds a child task to the group and set it up with the passed in task executor preference.
303279
///
304280
/// - Parameters:
305-
/// - taskExecutor: The task executor that the child task should be started on and keep using.
281+
/// - taskExecutorPreference: The task executor that the child task should be started on and keep using.
306282
/// If `nil` is passed explicitly, tht parent task's executor preference (if any),
307283
/// will be ignored. In order to inherit the parent task's executor preference
308284
/// invoke `_addTask()` without passing a value to the `taskExecutor` parameter,
@@ -313,7 +289,7 @@ extension ThrowingDiscardingTaskGroup {
313289
/// - operation: The operation to execute as part of the task group.
314290
@_alwaysEmitIntoClient
315291
public mutating func _addTask(
316-
on taskExecutor: (any _TaskExecutor)?,
292+
on taskExecutorPreference: any _TaskExecutor,
317293
priority: TaskPriority? = nil,
318294
operation: __owned @Sendable @escaping () async throws -> Void
319295
) {
@@ -325,11 +301,7 @@ extension ThrowingDiscardingTaskGroup {
325301
isDiscardingTask: true)
326302

327303
let executorBuiltin: Builtin.Executor =
328-
if let taskExecutor {
329-
taskExecutor.asUnownedTaskExecutor().executor
330-
} else {
331-
_getUndefinedTaskExecutor()
332-
}
304+
taskExecutorPreference.asUnownedTaskExecutor().executor
333305

334306
// Create the task in this group with an executor preference.
335307
_ = Builtin.createAsyncDiscardingTaskInGroupWithExecutor(flags, _group, executorBuiltin, operation)
@@ -342,7 +314,7 @@ extension ThrowingDiscardingTaskGroup {
342314
/// unless the group has been canceled.
343315
///
344316
/// - Parameters:
345-
/// - taskExecutor: The task executor that the child task should be started on and keep using.
317+
/// - taskExecutorPreference: The task executor that the child task should be started on and keep using.
346318
/// If `nil` is passed explicitly, tht parent task's executor preference (if any),
347319
/// will be ignored. In order to inherit the parent task's executor preference
348320
/// invoke `_addTask()` without passing a value to the `taskExecutor` parameter,
@@ -355,7 +327,7 @@ extension ThrowingDiscardingTaskGroup {
355327
/// otherwise `false`.
356328
@_alwaysEmitIntoClient
357329
public mutating func _addTaskUnlessCancelled(
358-
on taskExecutor: (any _TaskExecutor)?,
330+
on taskExecutorPreference: any _TaskExecutor,
359331
priority: TaskPriority? = nil,
360332
operation: __owned @Sendable @escaping () async throws -> Void
361333
) -> Bool {
@@ -373,11 +345,7 @@ extension ThrowingDiscardingTaskGroup {
373345
)
374346

375347
let executorBuiltin: Builtin.Executor =
376-
if let taskExecutor {
377-
taskExecutor.asUnownedTaskExecutor().executor
378-
} else {
379-
_getUndefinedTaskExecutor()
380-
}
348+
taskExecutorPreference.asUnownedTaskExecutor().executor
381349

382350
// Create the task in this group with an executor preference.
383351
_ = Builtin.createAsyncDiscardingTaskInGroupWithExecutor(flags, _group, executorBuiltin, operation)

0 commit comments

Comments
 (0)