@@ -29,6 +29,50 @@ import Swift
29
29
/// Passing `nil` as executor means disabling any preference preference (if it was set) and the task hierarchy
30
30
/// will execute without any executor preference until a different preference is set.
31
31
///
32
+ /// ### Asynchronous function execution semantics in presence of task executor preferences
33
+ /// The following diagram illustrates on which executor an `async` function will
34
+ /// execute, in presence (or lack thereof) a task executor preference.
35
+ ///
36
+ /// ```
37
+ /// [ func / closure ] - /* where should it execute? */
38
+ /// |
39
+ /// +--------------+ +===========================+
40
+ /// +-------- | is isolated? | - yes -> | actor has unownedExecutor |
41
+ /// | +--------------+ +===========================+
42
+ /// | | |
43
+ /// | yes no
44
+ /// | | |
45
+ /// | v v
46
+ /// | +=======================+ /* task executor preference? */
47
+ /// | | on specified executor | | |
48
+ /// | +=======================+ yes no
49
+ /// | | |
50
+ /// | | v
51
+ /// | | +==========================+
52
+ /// | | | default (actor) executor |
53
+ /// | v +==========================+
54
+ /// v +==============================+
55
+ /// /* task executor preference? */ ---- yes ----> | on Task's preferred executor |
56
+ /// | +==============================+
57
+ /// no
58
+ /// |
59
+ /// v
60
+ /// +===============================+
61
+ /// | on global concurrent executor |
62
+ /// +===============================+
63
+ /// ```
64
+ ///
65
+ /// In short, without a task executor preference, `nonisolated async` functions
66
+ /// will execute on the global concurrent executor. If a task executor preference
67
+ /// is present, such `nonisolated async` functions will execute on the preferred
68
+ /// task executor.
69
+ ///
70
+ /// Isolated functions semantically execute on the actor they are isolated to,
71
+ /// however if such actor does not declare a custom executor (it is a "default
72
+ /// actor") in presence of a task executor preference, tasks executing on this
73
+ /// actor will use the preferred executor as source of threads to run the task,
74
+ /// while isolated on the actor.
75
+ ///
32
76
/// ### Example
33
77
///
34
78
/// Task {
@@ -55,7 +99,7 @@ import Swift
55
99
/// async let x = ...
56
100
/// await withTaskGroup(of: Int.self) { group in
57
101
/// group.addTask { 7 } // child task executes on 'specific' executor
58
- /// group.addTask(on: .default ) { 13 } // child task executes on default executor
102
+ /// group.addTask(executorPreference: globalConcurrentExecutor ) { 13 } // child task executes on default executor
59
103
/// }
60
104
///
61
105
/// // disable the task executor preference:
@@ -75,20 +119,24 @@ import Swift
75
119
/// }
76
120
///
77
121
/// - Parameters:
78
- /// - executorPreference : the task executor to use as preferred task executor; if `nil` it is interpreted as "no preference"
122
+ /// - preferredTaskExecutor : the task executor to use as preferred task executor; if `nil` it is interpreted as "no preference"
79
123
/// - operation: the operation to execute on the passed executor; if the executor was `nil`, this will execute on the default global concurrent executor.
80
124
/// - Returns: the value returned from the `operation` closure
81
125
/// - Throws: if the operation closure throws
82
126
/// - SeeAlso: `_TaskExecutor`
83
127
@_unavailableInEmbedded
84
128
@available ( SwiftStdlib 9999 , * )
85
129
@_unsafeInheritExecutor // calling withTaskExecutor MUST NOT perform the "usual" hop to global
86
- public func _withTaskExecutor < T: Sendable > (
87
- _ taskExecutorPreference : some _TaskExecutor ,
130
+ public func _withTaskExecutorPreference < T: Sendable > (
131
+ _ taskExecutor : ( any _TaskExecutor ) ? ,
88
132
operation: @Sendable ( ) async throws -> T
89
133
) async rethrows -> T {
90
134
let taskExecutorBuiltin : Builtin . Executor =
91
- taskExecutorPreference. asUnownedTaskExecutor ( ) . executor
135
+ if let taskExecutor {
136
+ taskExecutor. asUnownedTaskExecutor ( ) . executor
137
+ } else {
138
+ globalConcurrentExecutor. asUnownedTaskExecutor ( ) . executor
139
+ }
92
140
93
141
let record = _pushTaskExecutorPreference ( taskExecutorBuiltin)
94
142
defer {
@@ -101,44 +149,6 @@ public func _withTaskExecutor<T: Sendable>(
101
149
return try await operation ( )
102
150
}
103
151
104
- /// Default global concurrent pool TaskExecutor --------------------------------
105
-
106
- @available ( SwiftStdlib 9999 , * )
107
- extension _TaskExecutor where Self == DefaultConcurrentExecutor {
108
-
109
- /// The default executor preference, setting it using ``withTaskExecutor(_:)``
110
- /// is equivalent to disabling an existing task executor preference, or
111
- /// stating that task now has "no task executor preference".
112
- @available ( SwiftStdlib 9999 , * )
113
- public static var `default` : DefaultConcurrentExecutor {
114
- DefaultConcurrentExecutor . shared
115
- }
116
-
117
- }
118
-
119
- /// A task executor which enqueues all work on the default global concurrent
120
- /// thread pool that is used as the default executor for Swift concurrency
121
- /// tasks.
122
- @available ( SwiftStdlib 9999 , * )
123
- public final class DefaultConcurrentExecutor : _TaskExecutor {
124
- public static let shared : DefaultConcurrentExecutor = . init( )
125
-
126
- private init ( ) { }
127
-
128
- public func enqueue( _ job: consuming ExecutorJob ) {
129
- _enqueueJobGlobal ( job. context)
130
- }
131
-
132
- public func asUnownedTaskExecutor( ) {
133
- // The "default global concurrent executor" is simply the "undefined" one.
134
- // We represent it as the `(0, 0)` ExecutorRef and it is handled properly
135
- // by the runtime, without having to call through to the
136
- // `DefaultConcurrentExecutor` declared in Swift.
137
- UnownedTaskExecutor ( _getUndefinedTaskExecutor ( ) )
138
- }
139
-
140
- }
141
-
142
152
/// Task with specified executor -----------------------------------------------
143
153
144
154
@available ( SwiftStdlib 9999 , * )
@@ -150,7 +160,7 @@ extension Task where Failure == Never {
150
160
/// This overload allows specifying a preferred ``_TaskExecutor`` on which
151
161
/// the `operation`, as well as all child tasks created from this task will be
152
162
/// executing whenever possible. Refer to ``_TaskExecutor`` for a detailed discussion
153
- // of the effect of task executors on execution semantics of asynchronous code.
163
+ /// of the effect of task executors on execution semantics of asynchronous code.
154
164
///
155
165
/// Use this function when creating asynchronous work
156
166
/// that operates on behalf of the synchronous function that calls it.
@@ -169,14 +179,14 @@ extension Task where Failure == Never {
169
179
/// it only makes it impossible for you to explicitly cancel the task.
170
180
///
171
181
/// - Parameters:
172
- /// - executor : the preferred task executor for this task, and any child tasks created by it
182
+ /// - taskExecutor : the preferred task executor for this task, and any child tasks created by it
173
183
/// - priority: The priority of the task.
174
184
/// Pass `nil` to use the priority from `Task.currentPriority`.
175
185
/// - operation: The operation to perform.
176
186
@discardableResult
177
187
@_alwaysEmitIntoClient
178
188
public init (
179
- _on executor : some _TaskExecutor ,
189
+ _executorPreference taskExecutor : ( any _TaskExecutor ) ? ,
180
190
priority: TaskPriority ? = nil ,
181
191
operation: __owned @Sendable @escaping ( ) async -> Success
182
192
) {
@@ -189,9 +199,15 @@ extension Task where Failure == Never {
189
199
isDiscardingTask: false )
190
200
191
201
// Create the asynchronous task.
192
- let taskExecutorRef = executor. asUnownedTaskExecutor ( )
202
+ let executorBuiltin : Builtin . Executor =
203
+ if let taskExecutor {
204
+ taskExecutor. asUnownedTaskExecutor ( ) . executor
205
+ } else {
206
+ globalConcurrentExecutor. asUnownedTaskExecutor ( ) . executor
207
+ }
208
+
193
209
let ( task, _) = Builtin . createAsyncTaskWithExecutor (
194
- flags, taskExecutorRef . executor , operation)
210
+ flags, executorBuiltin , operation)
195
211
self . _task = task
196
212
#else
197
213
fatalError ( " Unsupported Swift compiler, missing support for BuiltinCreateAsyncTaskWithExecutor " )
@@ -204,7 +220,7 @@ extension Task where Failure == Error {
204
220
@discardableResult
205
221
@_alwaysEmitIntoClient
206
222
public init (
207
- _on executor : some _TaskExecutor ,
223
+ _executorPreference taskExecutor : ( any _TaskExecutor ) ? ,
208
224
priority: TaskPriority ? = nil ,
209
225
operation: __owned @Sendable @escaping ( ) async throws -> Success
210
226
) {
@@ -217,9 +233,14 @@ extension Task where Failure == Error {
217
233
isDiscardingTask: false )
218
234
219
235
// Create the asynchronous task.
220
- let taskExecutorRef = executor. asUnownedTaskExecutor ( )
236
+ let executorBuiltin : Builtin . Executor =
237
+ if let taskExecutor {
238
+ taskExecutor. asUnownedTaskExecutor ( ) . executor
239
+ } else {
240
+ globalConcurrentExecutor. asUnownedTaskExecutor ( ) . executor
241
+ }
221
242
let ( task, _) = Builtin . createAsyncTaskWithExecutor (
222
- flags, taskExecutorRef . executor , operation)
243
+ flags, executorBuiltin , operation)
223
244
self . _task = task
224
245
#else
225
246
fatalError ( " Unsupported Swift compiler, missing support for $BuiltinCreateAsyncTaskWithExecutor " )
@@ -236,7 +257,7 @@ extension Task where Failure == Never {
236
257
@_alwaysEmitIntoClient
237
258
@available ( * , unavailable, message: " Unavailable in task-to-thread concurrency model " )
238
259
public static func _detached(
239
- on executor : some _TaskExecutor ,
260
+ _executorPreference taskExecutor : ( any _TaskExecutor ) ? ,
240
261
priority: TaskPriority ? = nil ,
241
262
operation: __owned @Sendable @escaping ( ) async -> Success
242
263
) -> Task < Success , Failure > {
@@ -267,7 +288,7 @@ extension Task where Failure == Never {
267
288
@discardableResult
268
289
@_alwaysEmitIntoClient
269
290
public static func _detached(
270
- on executor : some _TaskExecutor ,
291
+ _executorPreference taskExecutor : ( any _TaskExecutor ) ? ,
271
292
priority: TaskPriority ? = nil ,
272
293
operation: __owned @Sendable @escaping ( ) async -> Success
273
294
) -> Task < Success , Failure > {
@@ -280,9 +301,14 @@ extension Task where Failure == Never {
280
301
isDiscardingTask: false )
281
302
282
303
// Create the asynchronous task.
283
- let taskExecutorRef = executor. asUnownedTaskExecutor ( )
304
+ let executorBuiltin : Builtin . Executor =
305
+ if let taskExecutor {
306
+ taskExecutor. asUnownedTaskExecutor ( ) . executor
307
+ } else {
308
+ globalConcurrentExecutor. asUnownedTaskExecutor ( ) . executor
309
+ }
284
310
let ( task, _) = Builtin . createAsyncTaskWithExecutor (
285
- flags, taskExecutorRef . executor , operation)
311
+ flags, executorBuiltin , operation)
286
312
287
313
return Task ( task)
288
314
#else
@@ -299,7 +325,7 @@ extension Task where Failure == Error {
299
325
@_alwaysEmitIntoClient
300
326
@available ( * , unavailable, message: " Unavailable in task-to-thread concurrency model " )
301
327
public static func _detached(
302
- on executor : some _TaskExecutor ,
328
+ _executorPreference taskExecutor : ( any _TaskExecutor ) ? ,
303
329
priority: TaskPriority ? = nil ,
304
330
operation: __owned @Sendable @escaping ( ) async throws -> Success
305
331
) -> Task < Success , Failure > {
@@ -332,7 +358,7 @@ extension Task where Failure == Error {
332
358
@discardableResult
333
359
@_alwaysEmitIntoClient
334
360
public static func _detached(
335
- on executor : some _TaskExecutor ,
361
+ _executorPreference taskExecutor : ( any _TaskExecutor ) ? ,
336
362
priority: TaskPriority ? = nil ,
337
363
operation: __owned @Sendable @escaping ( ) async throws -> Success
338
364
) -> Task < Success , Failure > {
@@ -345,10 +371,14 @@ extension Task where Failure == Error {
345
371
isDiscardingTask: false )
346
372
347
373
// Create the asynchronous task.
348
- // Create the asynchronous task.
349
- let taskExecutorRef = executor. asUnownedTaskExecutor ( )
374
+ let executorBuiltin : Builtin . Executor =
375
+ if let taskExecutor {
376
+ taskExecutor. asUnownedTaskExecutor ( ) . executor
377
+ } else {
378
+ globalConcurrentExecutor. asUnownedTaskExecutor ( ) . executor
379
+ }
350
380
let ( task, _) = Builtin . createAsyncTaskWithExecutor (
351
- flags, taskExecutorRef . executor , operation)
381
+ flags, executorBuiltin , operation)
352
382
353
383
return Task ( task)
354
384
#else
0 commit comments