Skip to content

Commit b3f2b6d

Browse files
committed
[Concurrency] Unstructured, throwing and detached tasks with names
1 parent 8eae1e3 commit b3f2b6d

File tree

2 files changed

+312
-29
lines changed

2 files changed

+312
-29
lines changed

stdlib/public/Concurrency/Task.swift

Lines changed: 291 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -790,30 +790,6 @@ extension Task where Failure == Never {
790790
#endif
791791
}
792792

793-
@available(SwiftStdlib 6.2, *)
794-
extension Task where Success == Never, Failure == Error {
795-
796-
/// Returns the human-readable name of the current task,
797-
/// if it was set during the tasks' creation.
798-
///
799-
/// Tasks can be named during their creation, which can be helpful to identify
800-
/// unique tasks which may be created at same source locations, for example:
801-
///
802-
/// func process(items: [Int]) async {
803-
/// await withTaskGroup { group in
804-
/// for item in items {
805-
/// group.addTask(name: "process-\(item)") {
806-
/// await process(item)
807-
/// }
808-
/// }
809-
/// }
810-
/// }
811-
@available(SwiftStdlib 6.2, *)
812-
public static var name: String? {
813-
return _getCurrentTaskNameString()
814-
}
815-
}
816-
817793
@available(SwiftStdlib 5.1, *)
818794
extension Task where Failure == Error {
819795
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
@@ -877,6 +853,112 @@ extension Task where Failure == Error {
877853
#endif
878854
}
879855

856+
857+
@available(SwiftStdlib 6.2, *)
858+
extension Task where Failure == Error {
859+
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
860+
@discardableResult
861+
@_alwaysEmitIntoClient
862+
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
863+
public init(
864+
name: String? = nil,
865+
priority: TaskPriority? = nil,
866+
@_inheritActorContext @_implicitSelfCapture operation: sending @escaping @isolated(any) () async throws -> Success
867+
) {
868+
fatalError("Unavailable in task-to-thread concurrency model.")
869+
}
870+
#elseif $Embedded
871+
@discardableResult
872+
@_alwaysEmitIntoClient
873+
@available(SwiftStdlib 6.2, *)
874+
public init(
875+
name: String? = nil,
876+
// TaskExecutor is unavailable in embedded
877+
priority: TaskPriority? = nil,
878+
@_inheritActorContext @_implicitSelfCapture operation: sending @escaping () async throws -> Success
879+
) {
880+
// Set up the job flags for a new task.
881+
let flags = taskCreateFlags(
882+
priority: priority, isChildTask: false, copyTaskLocals: true,
883+
inheritContext: true, enqueueJob: true,
884+
addPendingGroupTaskUnconditionally: false,
885+
isDiscardingTask: false)
886+
887+
// Create the asynchronous task.
888+
let (task, _) = Builtin.createAsyncTask(flags, operation)
889+
890+
self._task = task
891+
}
892+
#else
893+
/// Runs the given nonthrowing operation asynchronously
894+
/// as part of a new top-level task on behalf of the current actor.
895+
///
896+
/// Use this function when creating asynchronous work
897+
/// that operates on behalf of the synchronous function that calls it.
898+
/// Like `Task.detached(priority:operation:)`,
899+
/// this function creates a separate, top-level task.
900+
/// Unlike `Task.detached(priority:operation:)`,
901+
/// the task created by `Task.init(priority:operation:)`
902+
/// inherits the priority and actor context of the caller,
903+
/// so the operation is treated more like an asynchronous extension
904+
/// to the synchronous operation.
905+
///
906+
/// You need to keep a reference to the task
907+
/// if you want to cancel it by calling the `Task.cancel()` method.
908+
/// Discarding your reference to a detached task
909+
/// doesn't implicitly cancel that task,
910+
/// it only makes it impossible for you to explicitly cancel the task.
911+
///
912+
/// - Parameters:
913+
/// - name: The high-level name given for this task
914+
/// - priority: The priority of the task.
915+
/// Pass `nil` to use the priority from `Task.currentPriority`.
916+
/// - operation: The operation to perform.
917+
@discardableResult
918+
@_alwaysEmitIntoClient
919+
@available(SwiftStdlib 6.2, *)
920+
public init(
921+
name: String? = nil,
922+
priority: TaskPriority? = nil,
923+
@_inheritActorContext @_implicitSelfCapture operation: sending @escaping @isolated(any) () async throws -> Success
924+
) {
925+
// Set up the job flags for a new task.
926+
let flags = taskCreateFlags(
927+
priority: priority, isChildTask: false, copyTaskLocals: true,
928+
inheritContext: true, enqueueJob: true,
929+
addPendingGroupTaskUnconditionally: false,
930+
isDiscardingTask: false)
931+
932+
// Create the asynchronous task.
933+
let builtinSerialExecutor =
934+
Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
935+
936+
var task: Builtin.NativeObject?
937+
#if $BuiltinCreateAsyncTaskName
938+
if var name {
939+
task =
940+
name.withUTF8 { nameBytes in
941+
Builtin.createTask(
942+
flags: flags,
943+
initialSerialExecutor: builtinSerialExecutor,
944+
taskName: nameBytes.baseAddress?._rawValue,
945+
operation: operation).0
946+
}
947+
}
948+
#endif
949+
if task == nil {
950+
// either no task name was set, or names are unsupported
951+
task = Builtin.createTask(
952+
flags: flags,
953+
initialSerialExecutor: builtinSerialExecutor,
954+
operation: operation).0
955+
}
956+
957+
self._task = task!
958+
}
959+
#endif
960+
}
961+
880962
// ==== Detached Tasks ---------------------------------------------------------
881963

882964
@available(SwiftStdlib 5.1, *)
@@ -939,6 +1021,85 @@ extension Task where Failure == Never {
9391021
#endif
9401022
}
9411023

1024+
@available(SwiftStdlib 6.2, *)
1025+
extension Task where Failure == Never {
1026+
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
1027+
@discardableResult
1028+
@_alwaysEmitIntoClient
1029+
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
1030+
public static func detached(
1031+
name: String? = nil,
1032+
priority: TaskPriority? = nil,
1033+
operation: sending @escaping @isolated(any) () async -> Success
1034+
) -> Task<Success, Failure> {
1035+
fatalError("Unavailable in task-to-thread concurrency model")
1036+
}
1037+
#else
1038+
/// Runs the given nonthrowing operation asynchronously
1039+
/// as part of a new top-level task.
1040+
///
1041+
/// Don't use a detached task if it's possible
1042+
/// to model the operation using structured concurrency features like child tasks.
1043+
/// Child tasks inherit the parent task's priority and task-local storage,
1044+
/// and canceling a parent task automatically cancels all of its child tasks.
1045+
/// You need to handle these considerations manually with a detached task.
1046+
///
1047+
/// You need to keep a reference to the detached task
1048+
/// if you want to cancel it by calling the `Task.cancel()` method.
1049+
/// Discarding your reference to a detached task
1050+
/// doesn't implicitly cancel that task,
1051+
/// it only makes it impossible for you to explicitly cancel the task.
1052+
///
1053+
/// - Parameters:
1054+
/// - name: Human readable name of the task.
1055+
/// - priority: The priority of the task.
1056+
/// - operation: The operation to perform.
1057+
///
1058+
/// - Returns: A reference to the task.
1059+
@discardableResult
1060+
@_alwaysEmitIntoClient
1061+
public static func detached(
1062+
name: String? = nil,
1063+
priority: TaskPriority? = nil,
1064+
operation: sending @escaping @isolated(any) () async -> Success
1065+
) -> Task<Success, Failure> {
1066+
// Set up the job flags for a new task.
1067+
let flags = taskCreateFlags(
1068+
priority: priority, isChildTask: false, copyTaskLocals: false,
1069+
inheritContext: false, enqueueJob: true,
1070+
addPendingGroupTaskUnconditionally: false,
1071+
isDiscardingTask: false)
1072+
1073+
// Create the asynchronous task.
1074+
let builtinSerialExecutor =
1075+
Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
1076+
1077+
var task: Builtin.NativeObject?
1078+
#if $BuiltinCreateAsyncTaskName
1079+
if var name {
1080+
task =
1081+
name.withUTF8 { nameBytes in
1082+
Builtin.createTask(
1083+
flags: flags,
1084+
initialSerialExecutor: builtinSerialExecutor,
1085+
taskName: nameBytes.baseAddress?._rawValue,
1086+
operation: operation).0
1087+
}
1088+
}
1089+
#endif
1090+
if task == nil {
1091+
// either no task name was set, or names are unsupported
1092+
task = Builtin.createTask(
1093+
flags: flags,
1094+
initialSerialExecutor: builtinSerialExecutor,
1095+
operation: operation).0
1096+
}
1097+
1098+
return Task(task!)
1099+
}
1100+
#endif
1101+
}
1102+
9421103
@available(SwiftStdlib 5.1, *)
9431104
extension Task where Failure == Error {
9441105
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
@@ -1001,6 +1162,112 @@ extension Task where Failure == Error {
10011162
#endif
10021163
}
10031164

1165+
@available(SwiftStdlib 6.2, *)
1166+
extension Task where Failure == Error {
1167+
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
1168+
@discardableResult
1169+
@_alwaysEmitIntoClient
1170+
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
1171+
public static func detached(
1172+
name: String? = nil,
1173+
priority: TaskPriority? = nil,
1174+
operation: sending @escaping @isolated(any) () async throws -> Success
1175+
) -> Task<Success, Failure> {
1176+
fatalError("Unavailable in task-to-thread concurrency model")
1177+
}
1178+
#else
1179+
/// Runs the given throwing operation asynchronously
1180+
/// as part of a new top-level task.
1181+
///
1182+
/// If the operation throws an error, this method propagates that error.
1183+
///
1184+
/// Don't use a detached task if it's possible
1185+
/// to model the operation using structured concurrency features like child tasks.
1186+
/// Child tasks inherit the parent task's priority and task-local storage,
1187+
/// and canceling a parent task automatically cancels all of its child tasks.
1188+
/// You need to handle these considerations manually with a detached task.
1189+
///
1190+
/// You need to keep a reference to the detached task
1191+
/// if you want to cancel it by calling the `Task.cancel()` method.
1192+
/// Discarding your reference to a detached task
1193+
/// doesn't implicitly cancel that task,
1194+
/// it only makes it impossible for you to explicitly cancel the task.
1195+
///
1196+
/// - Parameters:
1197+
/// - priority: The priority of the task.
1198+
/// - operation: The operation to perform.
1199+
///
1200+
/// - Returns: A reference to the task.
1201+
@discardableResult
1202+
@_alwaysEmitIntoClient
1203+
public static func detached(
1204+
name: String? = nil,
1205+
priority: TaskPriority? = nil,
1206+
operation: sending @escaping @isolated(any) () async throws -> Success
1207+
) -> Task<Success, Failure> {
1208+
// Set up the job flags for a new task.
1209+
let flags = taskCreateFlags(
1210+
priority: priority, isChildTask: false, copyTaskLocals: false,
1211+
inheritContext: false, enqueueJob: true,
1212+
addPendingGroupTaskUnconditionally: false,
1213+
isDiscardingTask: false)
1214+
1215+
// Create the asynchronous task future.
1216+
let builtinSerialExecutor =
1217+
Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
1218+
1219+
var task: Builtin.NativeObject?
1220+
#if $BuiltinCreateAsyncTaskName
1221+
if var name {
1222+
task =
1223+
name.withUTF8 { nameBytes in
1224+
Builtin.createTask(
1225+
flags: flags,
1226+
initialSerialExecutor: builtinSerialExecutor,
1227+
taskName: nameBytes.baseAddress?._rawValue,
1228+
operation: operation).0
1229+
}
1230+
}
1231+
#endif
1232+
if task == nil {
1233+
// either no task name was set, or names are unsupported
1234+
task = Builtin.createTask(
1235+
flags: flags,
1236+
initialSerialExecutor: builtinSerialExecutor,
1237+
operation: operation).0
1238+
}
1239+
1240+
return Task(task!)
1241+
}
1242+
#endif
1243+
}
1244+
1245+
// ==== Task Name --------------------------------------------------------------
1246+
1247+
@available(SwiftStdlib 6.2, *)
1248+
extension Task where Success == Never, Failure == Never {
1249+
1250+
/// Returns the human-readable name of the current task,
1251+
/// if it was set during the tasks' creation.
1252+
///
1253+
/// Tasks can be named during their creation, which can be helpful to identify
1254+
/// unique tasks which may be created at same source locations, for example:
1255+
///
1256+
/// func process(items: [Int]) async {
1257+
/// await withTaskGroup { group in
1258+
/// for item in items {
1259+
/// group.addTask(name: "process-\(item)") {
1260+
/// await process(item)
1261+
/// }
1262+
/// }
1263+
/// }
1264+
/// }
1265+
@available(SwiftStdlib 6.2, *)
1266+
public static var name: String? {
1267+
return _getCurrentTaskNameString()
1268+
}
1269+
}
1270+
10041271
// ==== Voluntary Suspension -----------------------------------------------------
10051272

10061273
@available(SwiftStdlib 5.1, *)

test/Concurrency/Runtime/async_task_naming.swift

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
// REQUIRES: concurrency_runtime
77
// UNSUPPORTED: back_deployment_runtime
88

9+
func pretendToThrow() throws {}
10+
911
func test() async {
1012
// CHECK: Task.name = NONE
1113
print("Task.name = \(Task.name ?? "NONE")")
@@ -16,11 +18,25 @@ func test() async {
1618
return 12
1719
}.value
1820

19-
// _ = await Task.detached(name: "Caplin the Detached Task") {
20-
// // CHECK: Task.name = Caplin the Detached Task
21-
// print("Task.name = \(Task.name ?? "NONE")")
22-
// return 12
23-
// }.value
21+
_ = try? await Task(name: "Caplin the Throwing Task") {
22+
// CHECK: Task.name = Caplin the Throwing Task
23+
print("Task.name = \(Task.name ?? "NONE")")
24+
try pretendToThrow()
25+
return 12
26+
}.value
27+
28+
_ = await Task.detached(name: "Caplin the Detached Task") {
29+
// CHECK: Task.name = Caplin the Detached Task
30+
print("Task.name = \(Task.name ?? "NONE")")
31+
return 12
32+
}.value
33+
34+
_ = try? await Task.detached(name: "Caplin the Detached Throwing Task") {
35+
// CHECK: Task.name = Caplin the Detached Task
36+
print("Task.name = \(Task.name ?? "NONE")")
37+
try pretendToThrow()
38+
return 12
39+
}.value
2440

2541
_ = await withTaskGroup(of: Int.self) { g in
2642
g.addTask(name: "Caplin the TaskGroup Task") {

0 commit comments

Comments
 (0)