1313import Swift
1414@_implementationOnly import SwiftConcurrencyInternalShims
1515
16- // ==== Task.immediate --------- ------------------------------------------------
16+ // ==== Task.startSynchronously ------------------------------------------------
1717
1818% METHOD_VARIANTS = [
1919% 'THROWING',
2020% 'NON_THROWING',
2121% ]
22- % for METHOD_VARIANT in METHOD_VARIANTS:
22+ % for THROWING_VARIANT in METHOD_VARIANTS:
2323
24- % IS_THROWING = METHOD_VARIANT == 'THROWING'
24+ % IS_THROWING = THROWING_VARIANT == 'THROWING'
2525% if IS_THROWING:
2626% FAILURE_TYPE = 'Error'
2727% THROWS = 'throws '
@@ -51,8 +51,38 @@ extension Task where Failure == ${FAILURE_TYPE} {
5151 ) -> Task<Success, ${FAILURE_TYPE}> {
5252 immediate(name: name, priority: priority, operation: operation)
5353 }
54+ }
55+
56+ % end
57+
58+ // ==== Task.immediate(Detached) ---------------------------------------------------------
59+
60+ % METHOD_VARIANTS = [
61+ % ('immediate', 'THROWING'),
62+ % ('immediate', 'NON_THROWING'),
63+ % ('immediateDetached', 'THROWING'),
64+ % ('immediateDetached', 'NON_THROWING'),
65+ % ]
66+ % for (METHOD_NAME, THROWING_VARIANT) in METHOD_VARIANTS:
67+
68+ % IS_THROWING = THROWING_VARIANT == 'THROWING'
69+ % IS_DETACHED = 'Detached' in METHOD_NAME
70+ % if IS_THROWING:
71+ % FAILURE_TYPE = 'Error'
72+ % THROWS = 'throws '
73+ % else:
74+ % FAILURE_TYPE = 'Never'
75+ % THROWS = ''
76+ % end
5477
78+ @available(SwiftStdlib 6.2, *)
79+ extension Task where Failure == ${FAILURE_TYPE} {
80+
81+ % if IS_DETACHED:
5582 /// Create and immediately start running a new task in the context of the calling thread/task.
83+ % else:
84+ /// Create and immediately start running a new detached task in the context of the calling thread/task.
85+ % end # IS_DETACHED
5686 ///
5787 /// This function _starts_ the created task on the calling context.
5888 /// The task will continue executing on the caller's context until it suspends,
@@ -65,21 +95,30 @@ extension Task where Failure == ${FAILURE_TYPE} {
6595 /// a synchronous manner.
6696 ///
6797 /// Other than the execution semantics discussed above, the created task
68- /// is semantically equivalent to its basic version which can be
69- /// created using ``Task/init``.
98+ /// is semantically equivalent to a task created using
99+ % if IS_DETACHED:
100+ /// the ``Task/detached`` function.
101+ % else:
102+ /// the ``Task/init`` initializer.
103+ % end
70104 ///
71105 /// - Parameters:
72106 /// - name: The high-level human-readable name given for this task
73107 /// - priority: The priority of the task.
74108 /// Pass `nil` to use the ``Task/basePriority`` of the current task (if there is one).
109+ /// - taskExecutor: The task executor that the child task should be started on and keep using.
110+ /// Explicitly passing `nil` as the executor preference is equivalent to no preference,
111+ /// and effectively means to inherit the outer context's executor preference.
112+ /// You can also pass the ``globalConcurrentExecutor`` global executor explicitly.
75113 /// - operation: the operation to be run immediately upon entering the task.
76114 /// - Returns: A reference to the unstructured task which may be awaited on.
77115 @available(SwiftStdlib 6.2, *)
78116 @_alwaysEmitIntoClient
79117 @discardableResult
80- public static func immediate (
118+ public static func ${METHOD_NAME} (
81119 name: String? = nil,
82120 priority: TaskPriority? = nil,
121+ executorPreference taskExecutor: consuming (any TaskExecutor)? = nil,
83122 @_implicitSelfCapture @_inheritActorContext(always) operation: sending @isolated(any) @escaping () async ${THROWS} -> Success
84123 ) -> Task<Success, ${FAILURE_TYPE}> {
85124
@@ -93,14 +132,14 @@ extension Task where Failure == ${FAILURE_TYPE} {
93132 if let builtinSerialExecutor {
94133 _taskIsCurrentExecutor(executor: builtinSerialExecutor, flags: flagsMustNotCrash)
95134 } else {
96- true // if there is not target executor, we can run synchronously
135+ true // if there is no target executor, we can run synchronously
97136 }
98137
99138 let flags = taskCreateFlags(
100139 priority: priority,
101140 isChildTask: false,
102- copyTaskLocals: true,
103- inheritContext: true,
141+ copyTaskLocals: ${' true' if not IS_DETACHED else 'false /* detached */'} ,
142+ inheritContext: ${' true' if not IS_DETACHED else 'false /* detached */'} ,
104143 enqueueJob: !canRunSynchronously,
105144 addPendingGroupTaskUnconditionally: false,
106145 isDiscardingTask: false,
@@ -110,6 +149,17 @@ extension Task where Failure == ${FAILURE_TYPE} {
110149 var task: Builtin.NativeObject?
111150 #if $BuiltinCreateAsyncTaskName
112151 if let name {
152+ #if $BuiltinCreateAsyncTaskOwnedTaskExecutor
153+ task =
154+ unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
155+ Builtin.createTask(
156+ flags: flags,
157+ initialSerialExecutor: builtinSerialExecutor,
158+ initialTaskExecutorConsuming: taskExecutor,
159+ taskName: nameBytes.baseAddress!._rawValue,
160+ operation: operation).0
161+ }
162+ #else // no $BuiltinCreateAsyncTaskOwnedTaskExecutor
113163 task =
114164 unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
115165 Builtin.createTask(
@@ -118,12 +168,37 @@ extension Task where Failure == ${FAILURE_TYPE} {
118168 taskName: nameBytes.baseAddress!._rawValue,
119169 operation: operation).0
120170 }
171+ #endif // $BuiltinCreateAsyncTaskOwnedTaskExecutor
172+ } // let name
173+ #endif // $BuiltinCreateAsyncTaskName
174+
175+ // Task name was not set, or task name createTask is unavailable
176+ if task == nil {
177+ assert(name == nil)
178+ #if $BuiltinCreateAsyncTaskOwnedTaskExecutor
179+ task = Builtin.createTask(
180+ flags: flags,
181+ initialSerialExecutor: builtinSerialExecutor,
182+ initialTaskExecutorConsuming: taskExecutor,
183+ operation: operation).0
184+ #else
185+ // legacy branch for the non-consuming task executor
186+ let executorBuiltin: Builtin.Executor =
187+ taskExecutor.asUnownedTaskExecutor().executor
188+
189+ task = Builtin.createTask(
190+ flags: flags,
191+ initialSerialExecutor: builtinSerialExecutor,
192+ initialTaskExecutor: executorBuiltin,
193+ operation: operation).0
194+ #endif
121195 }
122- #endif
196+
123197 if task == nil {
124198 // either no task name was set, or names are unsupported
125199 task = Builtin.createTask(
126200 flags: flags,
201+ initialSerialExecutor: builtinSerialExecutor,
127202 operation: operation).0
128203 }
129204
@@ -177,10 +252,22 @@ GROUP_AND_OP_INFO = [
177252}%
178253% for (GROUP_TYPE, METHOD_NAMES, THROWS, RESULT_TYPE) in GROUP_AND_OP_INFO:
179254% for METHOD_NAME in METHOD_NAMES:
255+ %
256+ % IS_DISCARDING = 'Discarding' in GROUP_TYPE
257+ % IS_ADD_UNLESS_CANCELLED = METHOD_NAME == "addImmediateTaskUnlessCancelled"
258+ %
259+ % ARROW_RETURN_TYPE = "-> Bool " if IS_ADD_UNLESS_CANCELLED else ""
260+ %
261+ % if IS_DISCARDING:
262+ % TASK_CREATE_FN = 'Builtin.createDiscardingTask'
263+ % else:
264+ % TASK_CREATE_FN = 'Builtin.createTask'
265+ % end
266+
180267@available(SwiftStdlib 6.2, *)
181268extension ${GROUP_TYPE} {
182269
183- /// Create and immediately start running a new child task in the context of the calling thread/task.
270+ /// Add a child task to the group and immediately start running it in the context of the calling thread/task.
184271 ///
185272 /// This function _starts_ the created task on the calling context.
186273 /// The task will continue executing on the caller's context until it suspends,
@@ -195,36 +282,116 @@ extension ${GROUP_TYPE} {
195282 /// Other than the execution semantics discussed above, the created task
196283 /// is semantically equivalent to its basic version which can be
197284 /// created using ``${GROUP_TYPE}/addTask``.
285+ ///
286+ /// - Parameters:
287+ /// - name: Human readable name of this task.
288+ /// - priority: The priority of the operation task.
289+ /// Omit this parameter or pass `nil` to inherit the task group's base priority.
290+ /// - taskExecutor: The task executor that the child task should be started on and keep using.
291+ /// Explicitly passing `nil` as the executor preference is equivalent to
292+ /// calling the `${METHOD_NAME}` method without a preference, and effectively
293+ /// means to inherit the outer context's executor preference.
294+ /// You can also pass the ``globalConcurrentExecutor`` global executor explicitly.
295+ /// - operation: The operation to execute as part of the task group.
296+ % if IS_ADD_UNLESS_CANCELLED:
297+ /// - Returns: `true` if the child task was added to the group;
298+ /// otherwise `false`.
299+ % end
198300 @available(SwiftStdlib 6.2, *)
199301 @_alwaysEmitIntoClient
200- public func ${METHOD_NAME}( // in ${GROUP_TYPE}
302+ public mutating func ${METHOD_NAME}( // in ${GROUP_TYPE}
201303 name: String? = nil,
202304 priority: TaskPriority? = nil,
305+ executorPreference taskExecutor: consuming (any TaskExecutor)? = nil,
203306 @_inheritActorContext @_implicitSelfCapture operation: sending @isolated(any) @escaping () async ${THROWS}-> ${RESULT_TYPE}
204- ) {
307+ ) ${ARROW_RETURN_TYPE}{
308+
309+ % if IS_ADD_UNLESS_CANCELLED:
310+ let canAdd = _taskGroupAddPendingTask(group: _group, unconditionally: false)
311+
312+ guard canAdd else {
313+ // the group is cancelled and is not accepting any new work
314+ return false
315+ }
316+ % end # IS_ADD_UNLESS_CANCELLED
317+
205318 let flags = taskCreateFlags(
206319 priority: priority,
207320 isChildTask: true,
208321 copyTaskLocals: false,
209322 inheritContext: false,
210323 enqueueJob: false, // don't enqueue, we'll run it manually
324+ % if IS_ADD_UNLESS_CANCELLED:
325+ % # In this case, we already added the pending task count before we create the task
326+ % # so we must not add to the pending counter again.
327+ addPendingGroupTaskUnconditionally: false,
328+ % else:
211329 addPendingGroupTaskUnconditionally: true,
212- isDiscardingTask: ${'true' if 'Discarding' in GROUP_TYPE else 'false'},
330+ % end
331+ isDiscardingTask: ${str(IS_DISCARDING).lower()},
213332 isSynchronousStart: true
214333 )
215334
216- // Create the asynchronous task.
217335 let builtinSerialExecutor =
218336 unsafe Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
219337
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)
338+ var task: Builtin.NativeObject?
339+
340+ #if $BuiltinCreateAsyncTaskName
341+ if let name {
342+ task =
343+ unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
344+ ${TASK_CREATE_FN}(
345+ flags: flags,
346+ initialSerialExecutor: builtinSerialExecutor,
347+ taskGroup: _group,
348+ initialTaskExecutorConsuming: taskExecutor,
349+ taskName: nameBytes.baseAddress!._rawValue,
350+ operation: operation).0
351+ }
352+ }
353+ #endif // $BuiltinCreateAsyncTaskName
354+
355+ // Task name was not set, or task name createTask is unavailable
356+ if task == nil, let taskExecutor {
357+ #if $BuiltinCreateAsyncTaskOwnedTaskExecutor
358+ task = ${TASK_CREATE_FN}(
359+ flags: flags,
360+ initialSerialExecutor: builtinSerialExecutor,
361+ taskGroup: _group,
362+ initialTaskExecutorConsuming: taskExecutor,
363+ operation: operation).0
364+ #else
365+ // legacy branch for the non-consuming task executor
366+ let executorBuiltin: Builtin.Executor =
367+ taskExecutor.asUnownedTaskExecutor().executor
368+
369+ task = ${TASK_CREATE_FN}(
370+ flags: flags,
371+ initialSerialExecutor: builtinSerialExecutor,
372+ taskGroup: _group,
373+ initialTaskExecutor: executorBuiltin,
374+ operation: operation).0
375+ #endif
376+ }
377+
378+ if task == nil {
379+ task = ${TASK_CREATE_FN}(
380+ flags: flags,
381+ initialSerialExecutor: builtinSerialExecutor,
382+ taskGroup: _group,
383+ operation: operation).0
384+ }
385+
386+ // Assert that we did create the task, but there's no need to store it,
387+ // as it was added to the group itself.
388+ assert(task != nil, "Expected task to be created!")
389+
390+ _startTaskImmediately(task!, targetExecutor: builtinSerialExecutor)
391+
392+ % if IS_ADD_UNLESS_CANCELLED:
393+ return true // task successfully enqueued
394+ % end
228395 }
229396}
230397% end # METHOD_NAMES
@@ -236,9 +403,9 @@ extension ${GROUP_TYPE} {
236403% 'THROWING',
237404% 'NON_THROWING',
238405% ]
239- % for METHOD_VARIANT in METHOD_VARIANTS:
406+ % for THROWING_VARIANT in METHOD_VARIANTS:
240407
241- % IS_THROWING = METHOD_VARIANT == 'THROWING'
408+ % IS_THROWING = THROWING_VARIANT == 'THROWING'
242409% if IS_THROWING:
243410% FAILURE_TYPE = 'Error'
244411% THROWS = 'throws '
@@ -255,6 +422,7 @@ extension Task where Failure == ${FAILURE_TYPE} {
255422 @MainActor
256423 @available(SwiftStdlib 5.9, *)
257424 @discardableResult
425+ @available(*, deprecated, renamed: "immediate")
258426 public static func startOnMainActor(
259427 priority: TaskPriority? = nil,
260428 @_inheritActorContext @_implicitSelfCapture _ operation: __owned @Sendable @escaping @MainActor () async ${THROWS} -> Success
0 commit comments