13
13
import Swift
14
14
@_implementationOnly import SwiftConcurrencyInternalShims
15
15
16
- // ==== Task.immediate --------- ------------------------------------------------
16
+ // ==== Task.startSynchronously ------------------------------------------------
17
17
18
18
% METHOD_VARIANTS = [
19
19
% 'THROWING',
20
20
% 'NON_THROWING',
21
21
% ]
22
- % for METHOD_VARIANT in METHOD_VARIANTS:
22
+ % for THROWING_VARIANT in METHOD_VARIANTS:
23
23
24
- % IS_THROWING = METHOD_VARIANT == 'THROWING'
24
+ % IS_THROWING = THROWING_VARIANT == 'THROWING'
25
25
% if IS_THROWING:
26
26
% FAILURE_TYPE = 'Error'
27
27
% THROWS = 'throws '
@@ -51,8 +51,38 @@ extension Task where Failure == ${FAILURE_TYPE} {
51
51
) -> Task<Success, ${FAILURE_TYPE}> {
52
52
immediate(name: name, priority: priority, operation: operation)
53
53
}
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
54
77
78
+ @available(SwiftStdlib 6.2, *)
79
+ extension Task where Failure == ${FAILURE_TYPE} {
80
+
81
+ % if IS_DETACHED:
55
82
/// 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
56
86
///
57
87
/// This function _starts_ the created task on the calling context.
58
88
/// The task will continue executing on the caller's context until it suspends,
@@ -65,21 +95,30 @@ extension Task where Failure == ${FAILURE_TYPE} {
65
95
/// a synchronous manner.
66
96
///
67
97
/// 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
70
104
///
71
105
/// - Parameters:
72
106
/// - name: The high-level human-readable name given for this task
73
107
/// - priority: The priority of the task.
74
108
/// 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.
75
113
/// - operation: the operation to be run immediately upon entering the task.
76
114
/// - Returns: A reference to the unstructured task which may be awaited on.
77
115
@available(SwiftStdlib 6.2, *)
78
116
@_alwaysEmitIntoClient
79
117
@discardableResult
80
- public static func immediate (
118
+ public static func ${METHOD_NAME} (
81
119
name: String? = nil,
82
120
priority: TaskPriority? = nil,
121
+ executorPreference taskExecutor: consuming (any TaskExecutor)? = nil,
83
122
@_implicitSelfCapture @_inheritActorContext(always) operation: sending @isolated(any) @escaping () async ${THROWS} -> Success
84
123
) -> Task<Success, ${FAILURE_TYPE}> {
85
124
@@ -93,14 +132,14 @@ extension Task where Failure == ${FAILURE_TYPE} {
93
132
if let builtinSerialExecutor {
94
133
_taskIsCurrentExecutor(executor: builtinSerialExecutor, flags: flagsMustNotCrash)
95
134
} 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
97
136
}
98
137
99
138
let flags = taskCreateFlags(
100
139
priority: priority,
101
140
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 */'} ,
104
143
enqueueJob: !canRunSynchronously,
105
144
addPendingGroupTaskUnconditionally: false,
106
145
isDiscardingTask: false,
@@ -110,6 +149,17 @@ extension Task where Failure == ${FAILURE_TYPE} {
110
149
var task: Builtin.NativeObject?
111
150
#if $BuiltinCreateAsyncTaskName
112
151
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
113
163
task =
114
164
unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
115
165
Builtin.createTask(
@@ -118,12 +168,37 @@ extension Task where Failure == ${FAILURE_TYPE} {
118
168
taskName: nameBytes.baseAddress!._rawValue,
119
169
operation: operation).0
120
170
}
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
121
195
}
122
- #endif
196
+
123
197
if task == nil {
124
198
// either no task name was set, or names are unsupported
125
199
task = Builtin.createTask(
126
200
flags: flags,
201
+ initialSerialExecutor: builtinSerialExecutor,
127
202
operation: operation).0
128
203
}
129
204
@@ -177,10 +252,22 @@ GROUP_AND_OP_INFO = [
177
252
}%
178
253
% for (GROUP_TYPE, METHOD_NAMES, THROWS, RESULT_TYPE) in GROUP_AND_OP_INFO:
179
254
% 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
+
180
267
@available(SwiftStdlib 6.2, *)
181
268
extension ${GROUP_TYPE} {
182
269
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.
184
271
///
185
272
/// This function _starts_ the created task on the calling context.
186
273
/// The task will continue executing on the caller's context until it suspends,
@@ -195,36 +282,116 @@ extension ${GROUP_TYPE} {
195
282
/// Other than the execution semantics discussed above, the created task
196
283
/// is semantically equivalent to its basic version which can be
197
284
/// 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
198
300
@available(SwiftStdlib 6.2, *)
199
301
@_alwaysEmitIntoClient
200
- public func ${METHOD_NAME}( // in ${GROUP_TYPE}
302
+ public mutating func ${METHOD_NAME}( // in ${GROUP_TYPE}
201
303
name: String? = nil,
202
304
priority: TaskPriority? = nil,
305
+ executorPreference taskExecutor: consuming (any TaskExecutor)? = nil,
203
306
@_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
+
205
318
let flags = taskCreateFlags(
206
319
priority: priority,
207
320
isChildTask: true,
208
321
copyTaskLocals: false,
209
322
inheritContext: false,
210
323
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:
211
329
addPendingGroupTaskUnconditionally: true,
212
- isDiscardingTask: ${'true' if 'Discarding' in GROUP_TYPE else 'false'},
330
+ % end
331
+ isDiscardingTask: ${str(IS_DISCARDING).lower()},
213
332
isSynchronousStart: true
214
333
)
215
334
216
- // Create the asynchronous task.
217
335
let builtinSerialExecutor =
218
336
unsafe Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
219
337
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
228
395
}
229
396
}
230
397
% end # METHOD_NAMES
@@ -236,9 +403,9 @@ extension ${GROUP_TYPE} {
236
403
% 'THROWING',
237
404
% 'NON_THROWING',
238
405
% ]
239
- % for METHOD_VARIANT in METHOD_VARIANTS:
406
+ % for THROWING_VARIANT in METHOD_VARIANTS:
240
407
241
- % IS_THROWING = METHOD_VARIANT == 'THROWING'
408
+ % IS_THROWING = THROWING_VARIANT == 'THROWING'
242
409
% if IS_THROWING:
243
410
% FAILURE_TYPE = 'Error'
244
411
% THROWS = 'throws '
@@ -255,6 +422,7 @@ extension Task where Failure == ${FAILURE_TYPE} {
255
422
@MainActor
256
423
@available(SwiftStdlib 5.9, *)
257
424
@discardableResult
425
+ @available(*, deprecated, renamed: "immediate")
258
426
public static func startOnMainActor(
259
427
priority: TaskPriority? = nil,
260
428
@_inheritActorContext @_implicitSelfCapture _ operation: __owned @Sendable @escaping @MainActor () async ${THROWS} -> Success
0 commit comments