@@ -72,6 +72,10 @@ extension Task where Failure == ${FAILURE_TYPE} {
7272 /// - name: The high-level human-readable name given for this task
7373 /// - priority: The priority of the task.
7474 /// 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.
7579 /// - operation: the operation to be run immediately upon entering the task.
7680 /// - Returns: A reference to the unstructured task which may be awaited on.
7781 @available(SwiftStdlib 6.2, *)
@@ -80,6 +84,7 @@ extension Task where Failure == ${FAILURE_TYPE} {
8084 public static func immediate(
8185 name: String? = nil,
8286 priority: TaskPriority? = nil,
87+ executorPreference taskExecutor: consuming (any TaskExecutor)? = nil,
8388 @_implicitSelfCapture @_inheritActorContext(always) operation: sending @isolated(any) @escaping () async ${THROWS} -> Success
8489 ) -> Task<Success, ${FAILURE_TYPE}> {
8590
@@ -110,20 +115,56 @@ extension Task where Failure == ${FAILURE_TYPE} {
110115 var task: Builtin.NativeObject?
111116 #if $BuiltinCreateAsyncTaskName
112117 if let name {
118+ #if $BuiltinCreateAsyncTaskOwnedTaskExecutor
113119 task =
114120 unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
115121 Builtin.createTask(
116122 flags: flags,
117123 initialSerialExecutor: builtinSerialExecutor,
124+ initialTaskExecutorConsuming: taskExecutor,
118125 taskName: nameBytes.baseAddress!._rawValue,
119126 operation: operation).0
120127 }
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
121161 }
122- #endif
162+
123163 if task == nil {
124164 // either no task name was set, or names are unsupported
125165 task = Builtin.createTask(
126166 flags: flags,
167+ initialSerialExecutor: builtinSerialExecutor,
127168 operation: operation).0
128169 }
129170
@@ -177,10 +218,22 @@ GROUP_AND_OP_INFO = [
177218}%
178219% for (GROUP_TYPE, METHOD_NAMES, THROWS, RESULT_TYPE) in GROUP_AND_OP_INFO:
179220% 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+
180233@available(SwiftStdlib 6.2, *)
181234extension ${GROUP_TYPE} {
182235
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.
184237 ///
185238 /// This function _starts_ the created task on the calling context.
186239 /// The task will continue executing on the caller's context until it suspends,
@@ -195,36 +248,116 @@ extension ${GROUP_TYPE} {
195248 /// Other than the execution semantics discussed above, the created task
196249 /// is semantically equivalent to its basic version which can be
197250 /// 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
198266 @available(SwiftStdlib 6.2, *)
199267 @_alwaysEmitIntoClient
200- public func ${METHOD_NAME}( // in ${GROUP_TYPE}
268+ public mutating func ${METHOD_NAME}( // in ${GROUP_TYPE}
201269 name: String? = nil,
202270 priority: TaskPriority? = nil,
271+ executorPreference taskExecutor: consuming (any TaskExecutor)? = nil,
203272 @_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+
205284 let flags = taskCreateFlags(
206285 priority: priority,
207286 isChildTask: true,
208287 copyTaskLocals: false,
209288 inheritContext: false,
210289 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:
211295 addPendingGroupTaskUnconditionally: true,
212- isDiscardingTask: ${'true' if 'Discarding' in GROUP_TYPE else 'false'},
296+ % end
297+ isDiscardingTask: ${str(IS_DISCARDING).lower()},
213298 isSynchronousStart: true
214299 )
215300
216- // Create the asynchronous task.
217301 let builtinSerialExecutor =
218302 unsafe Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
219303
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
228361 }
229362}
230363% end # METHOD_NAMES
@@ -255,6 +388,7 @@ extension Task where Failure == ${FAILURE_TYPE} {
255388 @MainActor
256389 @available(SwiftStdlib 5.9, *)
257390 @discardableResult
391+ @available(*, deprecated, renamed: "immediate")
258392 public static func startOnMainActor(
259393 priority: TaskPriority? = nil,
260394 @_inheritActorContext @_implicitSelfCapture _ operation: __owned @Sendable @escaping @MainActor () async ${THROWS} -> Success
0 commit comments