@@ -72,6 +72,10 @@ extension Task where Failure == ${FAILURE_TYPE} {
72
72
/// - name: The high-level human-readable name given for this task
73
73
/// - priority: The priority of the task.
74
74
/// Pass `nil` to use the ``Task/basePriority`` of the current task (if there is one).
75
+ /// - taskExecutor: The task executor that the child task should be started on and keep using.
76
+ /// Explicitly passing `nil` as the executor preference is equivalent to no preference,
77
+ /// and effectively means to inherit the outer context's executor preference.
78
+ /// You can also pass the ``globalConcurrentExecutor`` global executor explicitly.
75
79
/// - operation: the operation to be run immediately upon entering the task.
76
80
/// - Returns: A reference to the unstructured task which may be awaited on.
77
81
@available(SwiftStdlib 6.2, *)
@@ -80,6 +84,7 @@ extension Task where Failure == ${FAILURE_TYPE} {
80
84
public static func immediate(
81
85
name: String? = nil,
82
86
priority: TaskPriority? = nil,
87
+ executorPreference taskExecutor: consuming (any TaskExecutor)? = nil,
83
88
@_implicitSelfCapture @_inheritActorContext(always) operation: sending @isolated(any) @escaping () async ${THROWS} -> Success
84
89
) -> Task<Success, ${FAILURE_TYPE}> {
85
90
@@ -110,20 +115,56 @@ extension Task where Failure == ${FAILURE_TYPE} {
110
115
var task: Builtin.NativeObject?
111
116
#if $BuiltinCreateAsyncTaskName
112
117
if let name {
118
+ #if $BuiltinCreateAsyncTaskOwnedTaskExecutor
113
119
task =
114
120
unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
115
121
Builtin.createTask(
116
122
flags: flags,
117
123
initialSerialExecutor: builtinSerialExecutor,
124
+ initialTaskExecutorConsuming: taskExecutor,
118
125
taskName: nameBytes.baseAddress!._rawValue,
119
126
operation: operation).0
120
127
}
128
+ #else // no $BuiltinCreateAsyncTaskOwnedTaskExecutor
129
+ task =
130
+ unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
131
+ Builtin.createTask(
132
+ flags: flags,
133
+ initialSerialExecutor: builtinSerialExecutor,
134
+ taskName: nameBytes.baseAddress!._rawValue,
135
+ operation: operation).0
136
+ }
137
+ #endif // $BuiltinCreateAsyncTaskOwnedTaskExecutor
138
+ } // let name
139
+ #endif // $BuiltinCreateAsyncTaskName
140
+
141
+ // Task name was not set, or task name createTask is unavailable
142
+ if task == nil {
143
+ assert(name == nil)
144
+ #if $BuiltinCreateAsyncTaskOwnedTaskExecutor
145
+ task = Builtin.createTask(
146
+ flags: flags,
147
+ initialSerialExecutor: builtinSerialExecutor,
148
+ initialTaskExecutorConsuming: taskExecutor,
149
+ operation: operation).0
150
+ #else
151
+ // legacy branch for the non-consuming task executor
152
+ let executorBuiltin: Builtin.Executor =
153
+ taskExecutor.asUnownedTaskExecutor().executor
154
+
155
+ task = Builtin.createTask(
156
+ flags: flags,
157
+ initialSerialExecutor: builtinSerialExecutor,
158
+ initialTaskExecutor: executorBuiltin,
159
+ operation: operation).0
160
+ #endif
121
161
}
122
- #endif
162
+
123
163
if task == nil {
124
164
// either no task name was set, or names are unsupported
125
165
task = Builtin.createTask(
126
166
flags: flags,
167
+ initialSerialExecutor: builtinSerialExecutor,
127
168
operation: operation).0
128
169
}
129
170
@@ -177,10 +218,22 @@ GROUP_AND_OP_INFO = [
177
218
}%
178
219
% for (GROUP_TYPE, METHOD_NAMES, THROWS, RESULT_TYPE) in GROUP_AND_OP_INFO:
179
220
% for METHOD_NAME in METHOD_NAMES:
221
+ %
222
+ % IS_DISCARDING = 'Discarding' in GROUP_TYPE
223
+ % IS_ADD_UNLESS_CANCELLED = METHOD_NAME == "addImmediateTaskUnlessCancelled"
224
+ %
225
+ % ARROW_RETURN_TYPE = "-> Bool " if IS_ADD_UNLESS_CANCELLED else ""
226
+ %
227
+ % if IS_DISCARDING:
228
+ % TASK_CREATE_FN = 'Builtin.createDiscardingTask'
229
+ % else:
230
+ % TASK_CREATE_FN = 'Builtin.createTask'
231
+ % end
232
+
180
233
@available(SwiftStdlib 6.2, *)
181
234
extension ${GROUP_TYPE} {
182
235
183
- /// Create and immediately start running a new child task in the context of the calling thread/task.
236
+ /// Add a child task to the group and immediately start running it in the context of the calling thread/task.
184
237
///
185
238
/// This function _starts_ the created task on the calling context.
186
239
/// The task will continue executing on the caller's context until it suspends,
@@ -195,36 +248,116 @@ extension ${GROUP_TYPE} {
195
248
/// Other than the execution semantics discussed above, the created task
196
249
/// is semantically equivalent to its basic version which can be
197
250
/// created using ``${GROUP_TYPE}/addTask``.
251
+ ///
252
+ /// - Parameters:
253
+ /// - name: Human readable name of this task.
254
+ /// - priority: The priority of the operation task.
255
+ /// Omit this parameter or pass `nil` to inherit the task group's base priority.
256
+ /// - taskExecutor: The task executor that the child task should be started on and keep using.
257
+ /// Explicitly passing `nil` as the executor preference is equivalent to
258
+ /// calling the `${METHOD_NAME}` method without a preference, and effectively
259
+ /// means to inherit the outer context's executor preference.
260
+ /// You can also pass the ``globalConcurrentExecutor`` global executor explicitly.
261
+ /// - operation: The operation to execute as part of the task group.
262
+ % if IS_ADD_UNLESS_CANCELLED:
263
+ /// - Returns: `true` if the child task was added to the group;
264
+ /// otherwise `false`.
265
+ % end
198
266
@available(SwiftStdlib 6.2, *)
199
267
@_alwaysEmitIntoClient
200
- public func ${METHOD_NAME}( // in ${GROUP_TYPE}
268
+ public mutating func ${METHOD_NAME}( // in ${GROUP_TYPE}
201
269
name: String? = nil,
202
270
priority: TaskPriority? = nil,
271
+ executorPreference taskExecutor: consuming (any TaskExecutor)? = nil,
203
272
@_inheritActorContext @_implicitSelfCapture operation: sending @isolated(any) @escaping () async ${THROWS}-> ${RESULT_TYPE}
204
- ) {
273
+ ) ${ARROW_RETURN_TYPE}{
274
+
275
+ % if IS_ADD_UNLESS_CANCELLED:
276
+ let canAdd = _taskGroupAddPendingTask(group: _group, unconditionally: false)
277
+
278
+ guard canAdd else {
279
+ // the group is cancelled and is not accepting any new work
280
+ return false
281
+ }
282
+ % end # IS_ADD_UNLESS_CANCELLED
283
+
205
284
let flags = taskCreateFlags(
206
285
priority: priority,
207
286
isChildTask: true,
208
287
copyTaskLocals: false,
209
288
inheritContext: false,
210
289
enqueueJob: false, // don't enqueue, we'll run it manually
290
+ % if IS_ADD_UNLESS_CANCELLED:
291
+ % # In this case, we already added the pending task count before we create the task
292
+ % # so we must not add to the pending counter again.
293
+ addPendingGroupTaskUnconditionally: false,
294
+ % else:
211
295
addPendingGroupTaskUnconditionally: true,
212
- isDiscardingTask: ${'true' if 'Discarding' in GROUP_TYPE else 'false'},
296
+ % end
297
+ isDiscardingTask: ${str(IS_DISCARDING).lower()},
213
298
isSynchronousStart: true
214
299
)
215
300
216
- // Create the asynchronous task.
217
301
let builtinSerialExecutor =
218
302
unsafe Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
219
303
220
- // Create the task in this group.
221
- let (task, _) = Builtin.createTask(
222
- flags: flags,
223
- initialSerialExecutor: builtinSerialExecutor,
224
- taskGroup: self._group,
225
- operation: operation
226
- )
227
- _startTaskImmediately(task, targetExecutor: builtinSerialExecutor)
304
+ var task: Builtin.NativeObject?
305
+
306
+ #if $BuiltinCreateAsyncTaskName
307
+ if let name {
308
+ task =
309
+ unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
310
+ ${TASK_CREATE_FN}(
311
+ flags: flags,
312
+ initialSerialExecutor: builtinSerialExecutor,
313
+ taskGroup: _group,
314
+ initialTaskExecutorConsuming: taskExecutor,
315
+ taskName: nameBytes.baseAddress!._rawValue,
316
+ operation: operation).0
317
+ }
318
+ }
319
+ #endif // $BuiltinCreateAsyncTaskName
320
+
321
+ // Task name was not set, or task name createTask is unavailable
322
+ if task == nil, let taskExecutor {
323
+ #if $BuiltinCreateAsyncTaskOwnedTaskExecutor
324
+ task = ${TASK_CREATE_FN}(
325
+ flags: flags,
326
+ initialSerialExecutor: builtinSerialExecutor,
327
+ taskGroup: _group,
328
+ initialTaskExecutorConsuming: taskExecutor,
329
+ operation: operation).0
330
+ #else
331
+ // legacy branch for the non-consuming task executor
332
+ let executorBuiltin: Builtin.Executor =
333
+ taskExecutor.asUnownedTaskExecutor().executor
334
+
335
+ task = ${TASK_CREATE_FN}(
336
+ flags: flags,
337
+ initialSerialExecutor: builtinSerialExecutor,
338
+ taskGroup: _group,
339
+ initialTaskExecutor: executorBuiltin,
340
+ operation: operation).0
341
+ #endif
342
+ }
343
+
344
+ if task == nil {
345
+ task = ${TASK_CREATE_FN}(
346
+ flags: flags,
347
+ initialSerialExecutor: builtinSerialExecutor,
348
+ taskGroup: _group,
349
+ operation: operation).0
350
+ }
351
+
352
+ // Assert that we did create the task, but there's no need to store it,
353
+ // as it was added to the group itself.
354
+ assert(task != nil, "Expected task to be created!")
355
+
356
+ _startTaskImmediately(task!, targetExecutor: builtinSerialExecutor)
357
+
358
+ % if IS_ADD_UNLESS_CANCELLED:
359
+ return true // task successfully enqueued
360
+ % end
228
361
}
229
362
}
230
363
% end # METHOD_NAMES
@@ -255,6 +388,7 @@ extension Task where Failure == ${FAILURE_TYPE} {
255
388
@MainActor
256
389
@available(SwiftStdlib 5.9, *)
257
390
@discardableResult
391
+ @available(*, deprecated, renamed: "immediate")
258
392
public static func startOnMainActor(
259
393
priority: TaskPriority? = nil,
260
394
@_inheritActorContext @_implicitSelfCapture _ operation: __owned @Sendable @escaping @MainActor () async ${THROWS} -> Success
0 commit comments