mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
spawnDetached to detach, non-suspending group.spawn, spawnUnlessCancelled
This commit is contained in:
@@ -105,7 +105,7 @@ extension Task {
|
|||||||
/// ### Priority inheritance
|
/// ### Priority inheritance
|
||||||
/// Child tasks automatically inherit their parent task's priority.
|
/// Child tasks automatically inherit their parent task's priority.
|
||||||
///
|
///
|
||||||
/// Detached tasks (created by `spawnDetached`) DO NOT inherit task priority,
|
/// Detached tasks (created by `detach`) DO NOT inherit task priority,
|
||||||
/// as they are "detached" from their parent tasks after all.
|
/// as they are "detached" from their parent tasks after all.
|
||||||
///
|
///
|
||||||
/// ### Priority elevation
|
/// ### Priority elevation
|
||||||
@@ -150,7 +150,7 @@ extension Task {
|
|||||||
/// i.e. the task will run regardless of the handle still being present or not.
|
/// i.e. the task will run regardless of the handle still being present or not.
|
||||||
/// Dropping a handle however means losing the ability to await on the task's result
|
/// Dropping a handle however means losing the ability to await on the task's result
|
||||||
/// and losing the ability to cancel it.
|
/// and losing the ability to cancel it.
|
||||||
public struct Handle<Success, Failure: Error> {
|
public struct Handle<Success, Failure: Error>: Sendable {
|
||||||
internal let _task: Builtin.NativeObject
|
internal let _task: Builtin.NativeObject
|
||||||
|
|
||||||
internal init(_ task: Builtin.NativeObject) {
|
internal init(_ task: Builtin.NativeObject) {
|
||||||
@@ -395,7 +395,7 @@ extension Task {
|
|||||||
/// throw the error the operation has thrown when awaited on.
|
/// throw the error the operation has thrown when awaited on.
|
||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public func spawnDetached<T>(
|
public func detach<T>(
|
||||||
priority: Task.Priority = .default,
|
priority: Task.Priority = .default,
|
||||||
operation: __owned @Sendable @escaping () async -> T
|
operation: __owned @Sendable @escaping () async -> T
|
||||||
) -> Task.Handle<T, Never> {
|
) -> Task.Handle<T, Never> {
|
||||||
@@ -447,7 +447,7 @@ public func spawnDetached<T>(
|
|||||||
/// tasks result or `cancel` it. If the operation fails the handle will
|
/// tasks result or `cancel` it. If the operation fails the handle will
|
||||||
/// throw the error the operation has thrown when awaited on.
|
/// throw the error the operation has thrown when awaited on.
|
||||||
@discardableResult
|
@discardableResult
|
||||||
public func spawnDetached<T, Failure>(
|
public func detach<T, Failure>(
|
||||||
priority: Task.Priority = .default,
|
priority: Task.Priority = .default,
|
||||||
operation: __owned @Sendable @escaping () async throws -> T
|
operation: __owned @Sendable @escaping () async throws -> T
|
||||||
) -> Task.Handle<T, Failure> {
|
) -> Task.Handle<T, Failure> {
|
||||||
@@ -472,7 +472,7 @@ public func spawnDetached<T, Failure>(
|
|||||||
// TODO: remove this?
|
// TODO: remove this?
|
||||||
public func _runAsyncHandler(operation: @escaping () async -> ()) {
|
public func _runAsyncHandler(operation: @escaping () async -> ()) {
|
||||||
typealias ConcurrentFunctionType = @Sendable () async -> ()
|
typealias ConcurrentFunctionType = @Sendable () async -> ()
|
||||||
spawnDetached(
|
detach(
|
||||||
operation: unsafeBitCast(operation, to: ConcurrentFunctionType.self)
|
operation: unsafeBitCast(operation, to: ConcurrentFunctionType.self)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -629,7 +629,7 @@ public func _asyncMainDrainQueue() -> Never
|
|||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
public func _runAsyncMain(_ asyncFun: @escaping () async throws -> ()) {
|
public func _runAsyncMain(_ asyncFun: @escaping () async throws -> ()) {
|
||||||
#if os(Windows)
|
#if os(Windows)
|
||||||
spawnDetached {
|
detach {
|
||||||
do {
|
do {
|
||||||
try await asyncFun()
|
try await asyncFun()
|
||||||
exit(0)
|
exit(0)
|
||||||
@@ -647,7 +647,7 @@ public func _runAsyncMain(_ asyncFun: @escaping () async throws -> ()) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spawnDetached {
|
detach {
|
||||||
await _doMain(asyncFun)
|
await _doMain(asyncFun)
|
||||||
exit(0)
|
exit(0)
|
||||||
}
|
}
|
||||||
@@ -705,12 +705,12 @@ func _taskIsCancelled(_ task: Builtin.NativeObject) -> Bool
|
|||||||
@_alwaysEmitIntoClient
|
@_alwaysEmitIntoClient
|
||||||
@usableFromInline
|
@usableFromInline
|
||||||
internal func _runTaskForBridgedAsyncMethod(_ body: @escaping () async -> Void) {
|
internal func _runTaskForBridgedAsyncMethod(_ body: @escaping () async -> Void) {
|
||||||
// TODO: We can probably do better than spawnDetached
|
// TODO: We can probably do better than detach
|
||||||
// if we're already running on behalf of a task,
|
// if we're already running on behalf of a task,
|
||||||
// if the receiver of the method invocation is itself an Actor, or in other
|
// if the receiver of the method invocation is itself an Actor, or in other
|
||||||
// situations.
|
// situations.
|
||||||
#if compiler(>=5.5) && $Sendable
|
#if compiler(>=5.5) && $Sendable
|
||||||
spawnDetached { await body() }
|
detach { await body() }
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,10 +61,8 @@ import Swift
|
|||||||
/// - if the body returns normally:
|
/// - if the body returns normally:
|
||||||
/// - the group will await any not yet complete tasks,
|
/// - the group will await any not yet complete tasks,
|
||||||
/// - once the `withTaskGroup` returns the group is guaranteed to be empty.
|
/// - once the `withTaskGroup` returns the group is guaranteed to be empty.
|
||||||
/// - if the body throws:
|
|
||||||
/// - all tasks remaining in the group will be automatically cancelled.
|
|
||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
public func withTaskGroup<ChildTaskResult, GroupResult>(
|
public func withTaskGroup<ChildTaskResult: Sendable, GroupResult>(
|
||||||
of childTaskResultType: ChildTaskResult.Type,
|
of childTaskResultType: ChildTaskResult.Type,
|
||||||
returning returnType: GroupResult.Type = GroupResult.self,
|
returning returnType: GroupResult.Type = GroupResult.self,
|
||||||
body: (inout TaskGroup<ChildTaskResult>) async -> GroupResult
|
body: (inout TaskGroup<ChildTaskResult>) async -> GroupResult
|
||||||
@@ -144,7 +142,7 @@ public func withTaskGroup<ChildTaskResult, GroupResult>(
|
|||||||
/// - once the `withTaskGroup` returns the group is guaranteed to be empty.
|
/// - once the `withTaskGroup` returns the group is guaranteed to be empty.
|
||||||
/// - if the body throws:
|
/// - if the body throws:
|
||||||
/// - all tasks remaining in the group will be automatically cancelled.
|
/// - all tasks remaining in the group will be automatically cancelled.
|
||||||
public func withThrowingTaskGroup<ChildTaskResult, GroupResult>(
|
public func withThrowingTaskGroup<ChildTaskResult: Sendable, GroupResult>(
|
||||||
of childTaskResultType: ChildTaskResult.Type,
|
of childTaskResultType: ChildTaskResult.Type,
|
||||||
returning returnType: GroupResult.Type = GroupResult.self,
|
returning returnType: GroupResult.Type = GroupResult.self,
|
||||||
body: (inout ThrowingTaskGroup<ChildTaskResult, Error>) async throws -> GroupResult
|
body: (inout ThrowingTaskGroup<ChildTaskResult, Error>) async throws -> GroupResult
|
||||||
@@ -191,7 +189,7 @@ public func withThrowingTaskGroup<ChildTaskResult, GroupResult>(
|
|||||||
/// A task group serves as storage for dynamically spawned tasks.
|
/// A task group serves as storage for dynamically spawned tasks.
|
||||||
///
|
///
|
||||||
/// It is created by the `withTaskGroup` function.
|
/// It is created by the `withTaskGroup` function.
|
||||||
public struct TaskGroup<ChildTaskResult> {
|
public struct TaskGroup<ChildTaskResult: Sendable> {
|
||||||
|
|
||||||
private let _task: Builtin.NativeObject
|
private let _task: Builtin.NativeObject
|
||||||
/// Group task into which child tasks offer their results,
|
/// Group task into which child tasks offer their results,
|
||||||
@@ -225,13 +223,13 @@ public struct TaskGroup<ChildTaskResult> {
|
|||||||
@discardableResult
|
@discardableResult
|
||||||
public mutating func spawn(
|
public mutating func spawn(
|
||||||
overridingPriority priorityOverride: Task.Priority? = nil,
|
overridingPriority priorityOverride: Task.Priority? = nil,
|
||||||
operation: @Sendable @escaping () async -> ChildTaskResult
|
operation: __owned @Sendable @escaping () async -> ChildTaskResult
|
||||||
) async -> Bool {
|
) -> Self.Spawned {
|
||||||
let canAdd = _taskGroupAddPendingTask(group: _group)
|
let canAdd = _taskGroupAddPendingTask(group: _group)
|
||||||
|
|
||||||
guard canAdd else {
|
guard canAdd else {
|
||||||
// the group is cancelled and is not accepting any new work
|
// the group is cancelled and is not accepting any new work
|
||||||
return false
|
return Spawned(handle: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the job flags for a new task.
|
// Set up the job flags for a new task.
|
||||||
@@ -252,7 +250,22 @@ public struct TaskGroup<ChildTaskResult> {
|
|||||||
// Enqueue the resulting job.
|
// Enqueue the resulting job.
|
||||||
_enqueueJobGlobal(Builtin.convertTaskToJob(childTask))
|
_enqueueJobGlobal(Builtin.convertTaskToJob(childTask))
|
||||||
|
|
||||||
return true
|
return Spawned(handle: Task.Handle(childTask))
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Spawned: Sendable {
|
||||||
|
/// Returns `true` if the task was successfully spawned in the task group,
|
||||||
|
/// `false` otherwise which means that the group was already cancelled and
|
||||||
|
/// refused to accept spawn a new child task.
|
||||||
|
public var successfully: Bool { handle != nil }
|
||||||
|
|
||||||
|
/// Task handle for the spawned task group child task,
|
||||||
|
/// or `nil` if it was not spawned successfully.
|
||||||
|
public let handle: Task.Handle<ChildTaskResult, Never>?
|
||||||
|
|
||||||
|
init(handle: Task.Handle<ChildTaskResult, Never>?) {
|
||||||
|
self.handle = handle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the a child task that was added to the group to complete,
|
/// Wait for the a child task that was added to the group to complete,
|
||||||
@@ -296,8 +309,8 @@ public struct TaskGroup<ChildTaskResult> {
|
|||||||
/// Order of values returned by next() is *completion order*, and not
|
/// Order of values returned by next() is *completion order*, and not
|
||||||
/// submission order. I.e. if tasks are added to the group one after another:
|
/// submission order. I.e. if tasks are added to the group one after another:
|
||||||
///
|
///
|
||||||
/// await group.spawn { 1 }
|
/// group.spawn { 1 }
|
||||||
/// await group.spawn { 2 }
|
/// group.spawn { 2 }
|
||||||
///
|
///
|
||||||
/// print(await group.next())
|
/// print(await group.next())
|
||||||
/// /// Prints "1" OR "2"
|
/// /// Prints "1" OR "2"
|
||||||
@@ -388,7 +401,7 @@ public struct TaskGroup<ChildTaskResult> {
|
|||||||
/// child tasks.
|
/// child tasks.
|
||||||
///
|
///
|
||||||
/// It is created by the `withTaskGroup` function.
|
/// It is created by the `withTaskGroup` function.
|
||||||
public struct ThrowingTaskGroup<ChildTaskResult, Failure: Error> {
|
public struct ThrowingTaskGroup<ChildTaskResult: Sendable, Failure: Error> {
|
||||||
|
|
||||||
private let _task: Builtin.NativeObject
|
private let _task: Builtin.NativeObject
|
||||||
/// Group task into which child tasks offer their results,
|
/// Group task into which child tasks offer their results,
|
||||||
@@ -423,12 +436,12 @@ public struct ThrowingTaskGroup<ChildTaskResult, Failure: Error> {
|
|||||||
public mutating func spawn(
|
public mutating func spawn(
|
||||||
overridingPriority priorityOverride: Task.Priority? = nil,
|
overridingPriority priorityOverride: Task.Priority? = nil,
|
||||||
operation: __owned @Sendable @escaping () async throws -> ChildTaskResult
|
operation: __owned @Sendable @escaping () async throws -> ChildTaskResult
|
||||||
) async -> Bool {
|
) -> Self.Spawned {
|
||||||
let canAdd = _taskGroupAddPendingTask(group: _group)
|
let canAdd = _taskGroupAddPendingTask(group: _group)
|
||||||
|
|
||||||
guard canAdd else {
|
guard canAdd else {
|
||||||
// the group is cancelled and is not accepting any new work
|
// the group is cancelled and is not accepting any new work
|
||||||
return false
|
return Spawned(handle: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up the job flags for a new task.
|
// Set up the job flags for a new task.
|
||||||
@@ -449,7 +462,22 @@ public struct ThrowingTaskGroup<ChildTaskResult, Failure: Error> {
|
|||||||
// Enqueue the resulting job.
|
// Enqueue the resulting job.
|
||||||
_enqueueJobGlobal(Builtin.convertTaskToJob(childTask))
|
_enqueueJobGlobal(Builtin.convertTaskToJob(childTask))
|
||||||
|
|
||||||
return true
|
return Spawned(handle: Task.Handle(childTask))
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct Spawned: Sendable {
|
||||||
|
/// Returns `true` if the task was successfully spawned in the task group,
|
||||||
|
/// `false` otherwise which means that the group was already cancelled and
|
||||||
|
/// refused to accept spawn a new child task.
|
||||||
|
public var successfully: Bool { handle != nil }
|
||||||
|
|
||||||
|
/// Task handle for the spawned task group child task,
|
||||||
|
/// or `nil` if it was not spawned successfully.
|
||||||
|
public let handle: Task.Handle<ChildTaskResult, Error>?
|
||||||
|
|
||||||
|
init(handle: Task.Handle<ChildTaskResult, Error>?) {
|
||||||
|
self.handle = handle
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Wait for the a child task that was added to the group to complete,
|
/// Wait for the a child task that was added to the group to complete,
|
||||||
@@ -493,8 +521,8 @@ public struct ThrowingTaskGroup<ChildTaskResult, Failure: Error> {
|
|||||||
/// Order of values returned by next() is *completion order*, and not
|
/// Order of values returned by next() is *completion order*, and not
|
||||||
/// submission order. I.e. if tasks are added to the group one after another:
|
/// submission order. I.e. if tasks are added to the group one after another:
|
||||||
///
|
///
|
||||||
/// await group.spawn { 1 }
|
/// group.spawn { 1 }
|
||||||
/// await group.spawn { 2 }
|
/// group.spawn { 2 }
|
||||||
///
|
///
|
||||||
/// print(await group.next())
|
/// print(await group.next())
|
||||||
/// /// Prints "1" OR "2"
|
/// /// Prints "1" OR "2"
|
||||||
@@ -519,6 +547,29 @@ public struct ThrowingTaskGroup<ChildTaskResult, Failure: Error> {
|
|||||||
return try await _taskGroupWaitNext(group: _group)
|
return try await _taskGroupWaitNext(group: _group)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// - SeeAlso: `next()`
|
||||||
|
public mutating func nextResult() async throws -> Result<ChildTaskResult, Failure>? {
|
||||||
|
#if NDEBUG
|
||||||
|
let callingTask = Builtin.getCurrentAsyncTask() // can't inline into the assert sadly
|
||||||
|
assert(unsafeBitCast(callingTask, to: size_t.self) ==
|
||||||
|
unsafeBitCast(_task, to: size_t.self),
|
||||||
|
"""
|
||||||
|
group.next() invoked from task other than the task which created the group! \
|
||||||
|
This means the group must have illegally escaped the withTaskGroup{} scope.
|
||||||
|
""")
|
||||||
|
#endif
|
||||||
|
|
||||||
|
do {
|
||||||
|
guard let success: ChildTaskResult = try await _taskGroupWaitNext(group: _group) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return .success(success)
|
||||||
|
} catch {
|
||||||
|
return .failure(error as! Failure) // as!-safe, because we are only allowed to throw Failure (Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Query whether the group has any remaining tasks.
|
/// Query whether the group has any remaining tasks.
|
||||||
///
|
///
|
||||||
/// Task groups are always empty upon entry to the `withTaskGroup` body, and
|
/// Task groups are always empty upon entry to the `withTaskGroup` body, and
|
||||||
@@ -650,6 +701,7 @@ extension ThrowingTaskGroup: AsyncSequence {
|
|||||||
|
|
||||||
/// - SeeAlso: `ThrowingTaskGroup.next()` for a detailed discussion its semantics.
|
/// - SeeAlso: `ThrowingTaskGroup.next()` for a detailed discussion its semantics.
|
||||||
public mutating func next() async throws -> Element? {
|
public mutating func next() async throws -> Element? {
|
||||||
|
guard !finished else { return nil }
|
||||||
do {
|
do {
|
||||||
guard let element = try await group.next() else {
|
guard let element = try await group.next() else {
|
||||||
finished = true
|
finished = true
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ func runTest(numCounters: Int, numWorkers: Int, numIterations: Int) async {
|
|||||||
var workers: [Task.Handle<Void, Error>] = []
|
var workers: [Task.Handle<Void, Error>] = []
|
||||||
for i in 0..<numWorkers {
|
for i in 0..<numWorkers {
|
||||||
workers.append(
|
workers.append(
|
||||||
spawnDetached { [counters] in
|
detach { [counters] in
|
||||||
await Task.sleep(UInt64.random(in: 0..<100) * 1_000_000)
|
await Task.sleep(UInt64.random(in: 0..<100) * 1_000_000)
|
||||||
await worker(identity: i, counters: counters, numIterations: numIterations)
|
await worker(identity: i, counters: counters, numIterations: numIterations)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
import Dispatch
|
import Dispatch
|
||||||
|
|
||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func test_spawnDetached_cancel_child_early() async {
|
func test_detach_cancel_child_early() async {
|
||||||
print(#function) // CHECK: test_spawnDetached_cancel_child_early
|
print(#function) // CHECK: test_detach_cancel_child_early
|
||||||
let h: Task.Handle<Bool, Error> = spawnDetached {
|
let h: Task.Handle<Bool, Error> = detach {
|
||||||
async let childCancelled: Bool = { () -> Bool in
|
async let childCancelled: Bool = { () -> Bool in
|
||||||
await Task.sleep(2_000_000_000)
|
await Task.sleep(2_000_000_000)
|
||||||
return Task.isCancelled
|
return Task.isCancelled
|
||||||
@@ -37,6 +37,6 @@ func test_spawnDetached_cancel_child_early() async {
|
|||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
@main struct Main {
|
@main struct Main {
|
||||||
static func main() async {
|
static func main() async {
|
||||||
await test_spawnDetached_cancel_child_early()
|
await test_detach_cancel_child_early()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
import Dispatch
|
import Dispatch
|
||||||
|
|
||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func test_spawnDetached_cancel_while_child_running() async {
|
func test_detach_cancel_while_child_running() async {
|
||||||
let h: Task.Handle<Bool, Error> = spawnDetached {
|
let h: Task.Handle<Bool, Error> = detach {
|
||||||
async let childCancelled: Bool = { () -> Bool in
|
async let childCancelled: Bool = { () -> Bool in
|
||||||
await Task.sleep(3_000_000_000)
|
await Task.sleep(3_000_000_000)
|
||||||
return Task.isCancelled
|
return Task.isCancelled
|
||||||
@@ -36,6 +36,6 @@ func test_spawnDetached_cancel_while_child_running() async {
|
|||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
@main struct Main {
|
@main struct Main {
|
||||||
static func main() async {
|
static func main() async {
|
||||||
await test_spawnDetached_cancel_while_child_running()
|
await test_detach_cancel_while_child_running()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,12 @@
|
|||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func simple() async {
|
func simple() async {
|
||||||
print("\(#function) -----------------------")
|
print("\(#function) -----------------------")
|
||||||
let one = await Task.current()
|
let one = Task.current!
|
||||||
let two = await Task.current()
|
let two = Task.current!
|
||||||
print("same equal: \(one == two)") // CHECK: same equal: true
|
print("same equal: \(one == two)") // CHECK: same equal: true
|
||||||
print("hashes equal: \(one.hashValue == two.hashValue)") // CHECK: hashes equal: true
|
print("hashes equal: \(one.hashValue == two.hashValue)") // CHECK: hashes equal: true
|
||||||
|
|
||||||
async let x = Task.current()
|
async let x = Task.current
|
||||||
let three = await x
|
let three = await x
|
||||||
|
|
||||||
print("parent/child equal: \(three == two)") // CHECK: parent/child equal: false
|
print("parent/child equal: \(three == two)") // CHECK: parent/child equal: false
|
||||||
@@ -26,12 +26,12 @@ func simple() async {
|
|||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func unsafe() async {
|
func unsafe() async {
|
||||||
print("\(#function) -----------------------")
|
print("\(#function) -----------------------")
|
||||||
let one = Task.unsafeCurrent!
|
let one = withUnsafeCurrentTask { $0! }
|
||||||
let two = Task.unsafeCurrent!
|
let two = withUnsafeCurrentTask { $0! }
|
||||||
print("unsafe same equal: \(one == two)") // CHECK: same equal: true
|
print("unsafe same equal: \(one == two)") // CHECK: same equal: true
|
||||||
print("unsafe hashes equal: \(one.hashValue == two.hashValue)") // CHECK: hashes equal: true
|
print("unsafe hashes equal: \(one.hashValue == two.hashValue)") // CHECK: hashes equal: true
|
||||||
|
|
||||||
async let x = Task.unsafeCurrent!
|
async let x = withUnsafeCurrentTask { $0! }
|
||||||
let three = await x
|
let three = await x
|
||||||
|
|
||||||
print("unsafe parent/child equal: \(three == two)") // CHECK: parent/child equal: false
|
print("unsafe parent/child equal: \(three == two)") // CHECK: parent/child equal: false
|
||||||
@@ -44,8 +44,8 @@ func unsafe() async {
|
|||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func unsafeSync() {
|
func unsafeSync() {
|
||||||
print("\(#function) -----------------------")
|
print("\(#function) -----------------------")
|
||||||
let one = Task.unsafeCurrent!
|
let one = withUnsafeCurrentTask { $0! }
|
||||||
let two = Task.unsafeCurrent!
|
let two = withUnsafeCurrentTask { $0! }
|
||||||
print("unsafe same equal: \(one == two)") // CHECK: same equal: true
|
print("unsafe same equal: \(one == two)") // CHECK: same equal: true
|
||||||
print("unsafe hashes equal: \(one.hashValue == two.hashValue)") // CHECK: hashes equal: true
|
print("unsafe hashes equal: \(one.hashValue == two.hashValue)") // CHECK: hashes equal: true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
@main struct Main {
|
@main struct Main {
|
||||||
static func main() async {
|
static func main() async {
|
||||||
let handle = spawnDetached {
|
let handle = detach {
|
||||||
while (!Task.isCancelled) { // no need for await here, yay
|
while (!Task.isCancelled) { // no need for await here, yay
|
||||||
print("waiting")
|
print("waiting")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func groups() async {
|
|||||||
|
|
||||||
// no value in parent, value in child
|
// no value in parent, value in child
|
||||||
let x1: Int = try! await withTaskGroup(of: Int.self) { group in
|
let x1: Int = try! await withTaskGroup(of: Int.self) { group in
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
printTaskLocal(\.number) // CHECK: NumberKey: 0 {{.*}}
|
printTaskLocal(\.number) // CHECK: NumberKey: 0 {{.*}}
|
||||||
// inside the child task, set a value
|
// inside the child task, set a value
|
||||||
await Task.withLocal(\.number, boundTo: 1) {
|
await Task.withLocal(\.number, boundTo: 1) {
|
||||||
@@ -71,7 +71,7 @@ func groups() async {
|
|||||||
|
|
||||||
let x2: Int = try! await withTaskGroup(of: Int.self) { group in
|
let x2: Int = try! await withTaskGroup(of: Int.self) { group in
|
||||||
printTaskLocal(\.number) // CHECK: NumberKey: 2 {{.*}}
|
printTaskLocal(\.number) // CHECK: NumberKey: 2 {{.*}}
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
printTaskLocal(\.number) // CHECK: NumberKey: 2 {{.*}}
|
printTaskLocal(\.number) // CHECK: NumberKey: 2 {{.*}}
|
||||||
|
|
||||||
async let childInsideGroupChild: () = printTaskLocal(\.number)
|
async let childInsideGroupChild: () = printTaskLocal(\.number)
|
||||||
|
|||||||
@@ -68,14 +68,15 @@ func test_async_group() async {
|
|||||||
await Task.withLocal(\.string, boundTo: "top") {
|
await Task.withLocal(\.string, boundTo: "top") {
|
||||||
printTaskLocal(\.string) // CHECK: StringKey: top {{.*}}
|
printTaskLocal(\.string) // CHECK: StringKey: top {{.*}}
|
||||||
|
|
||||||
try! await withTaskGroup(of: Void.self) { group -> Void? in
|
await withTaskGroup(of: Int.self, returning: Void.self) { group in
|
||||||
printTaskLocal(\.string) // CHECK: StringKey: top {{.*}}
|
printTaskLocal(\.string) // CHECK: StringKey: top {{.*}}
|
||||||
|
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
printTaskLocal(\.string) // CHECK: StringKey: <undefined> {{.*}}
|
printTaskLocal(\.string) // CHECK: StringKey: <undefined> {{.*}}
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
return try! await group.next()
|
_ = await group.next()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func test_detach() async {
|
|||||||
// Note: remember to detach using a higher priority, otherwise a lower one
|
// Note: remember to detach using a higher priority, otherwise a lower one
|
||||||
// might be escalated by the get() and we could see `default` in the detached
|
// might be escalated by the get() and we could see `default` in the detached
|
||||||
// task.
|
// task.
|
||||||
await spawnDetached(priority: .userInitiated) {
|
await detach(priority: .userInitiated) {
|
||||||
let a2 = Task.currentPriority
|
let a2 = Task.currentPriority
|
||||||
print("a2: \(a2)") // CHECK: a2: userInitiated
|
print("a2: \(a2)") // CHECK: a2: userInitiated
|
||||||
}.get()
|
}.get()
|
||||||
@@ -35,16 +35,16 @@ func test_multiple_lo_indirectly_escalated() async {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let z = spawnDetached(priority: .background) {
|
let z = detach(priority: .background) {
|
||||||
await loopUntil(priority: .userInitiated)
|
await loopUntil(priority: .userInitiated)
|
||||||
}
|
}
|
||||||
let x = spawnDetached(priority: .background) {
|
let x = detach(priority: .background) {
|
||||||
_ = await z // waiting on `z`, but it won't complete since we're also background
|
_ = await z // waiting on `z`, but it won't complete since we're also background
|
||||||
await loopUntil(priority: .userInitiated)
|
await loopUntil(priority: .userInitiated)
|
||||||
}
|
}
|
||||||
|
|
||||||
// detach, don't wait
|
// detach, don't wait
|
||||||
spawnDetached(priority: .userInitiated) {
|
detach(priority: .userInitiated) {
|
||||||
await x // escalates x, which waits on z, so z also escalates
|
await x // escalates x, which waits on z, so z also escalates
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import Dispatch
|
|||||||
|
|
||||||
static func testSleepDoesNotBlock() async {
|
static func testSleepDoesNotBlock() async {
|
||||||
// FIXME: Should run on main executor
|
// FIXME: Should run on main executor
|
||||||
let task = spawnDetached {
|
let task = detach {
|
||||||
print("Run first")
|
print("Run first")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
// REQUIRES: concurrency
|
// REQUIRES: concurrency
|
||||||
// XFAIL: linux
|
// XFAIL: linux
|
||||||
// XFAIL: windows
|
// XFAIL: windows
|
||||||
|
|
||||||
struct Boom: Error {}
|
struct Boom: Error {}
|
||||||
|
|
||||||
func boom() async throws -> Int {
|
func boom() async throws -> Int {
|
||||||
@@ -10,12 +11,10 @@ func boom() async throws -> Int {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func test_taskGroup_next() async {
|
func test_taskGroup_next() async {
|
||||||
let sum: Int = await Task.withGroup(resultType: Int.self) { group in
|
let sum = await withThrowingTaskGroup(of: Int.self, returning: Int.self) { group in
|
||||||
for n in 1...10 {
|
for n in 1...10 {
|
||||||
await group.add {
|
group.spawn {
|
||||||
return n.isMultiple(of: 3)
|
return n.isMultiple(of: 3) ? try await boom() : n
|
||||||
? try await boom()
|
|
||||||
: n
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,12 +41,10 @@ func test_taskGroup_next() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func test_taskGroup_for_in() async {
|
func test_taskGroup_for_in() async {
|
||||||
let sum: Int = await Task.withGroup(resultType: Int.self) { group in
|
let sum = await withThrowingTaskGroup(of: Int.self, returning: Int.self) { group in
|
||||||
for n in 1...10 {
|
for n in 1...10 {
|
||||||
await group.add {
|
group.spawn {
|
||||||
return n.isMultiple(of: 3)
|
return n.isMultiple(of: 3) ? try await boom() : n
|
||||||
? try await boom()
|
|
||||||
: n
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,12 +71,10 @@ func test_taskGroup_for_in() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func test_taskGroup_asyncIterator() async {
|
func test_taskGroup_asyncIterator() async {
|
||||||
let sum: Int = await Task.withGroup(resultType: Int.self) { group in
|
let sum = await withThrowingTaskGroup(of: Int.self, returning: Int.self) { group in
|
||||||
for n in 1...10 {
|
for n in 1...10 {
|
||||||
await group.add {
|
group.spawn {
|
||||||
return n.isMultiple(of: 3)
|
return n.isMultiple(of: 3) ? try await boom() : n
|
||||||
? try await boom()
|
|
||||||
: n
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func test_taskGroup_cancelAll_onlySpecificGroup() async {
|
|||||||
async let g1: Int = withTaskGroup(of: Int.self) { group in
|
async let g1: Int = withTaskGroup(of: Int.self) { group in
|
||||||
|
|
||||||
for i in 1...5 {
|
for i in 1...5 {
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
await Task.sleep(1_000_000_000)
|
await Task.sleep(1_000_000_000)
|
||||||
let c = Task.isCancelled
|
let c = Task.isCancelled
|
||||||
print("add: \(i) (cancelled: \(c))")
|
print("add: \(i) (cancelled: \(c))")
|
||||||
@@ -46,7 +46,7 @@ func test_taskGroup_cancelAll_onlySpecificGroup() async {
|
|||||||
// The cancellation os g2 should have no impact on g1
|
// The cancellation os g2 should have no impact on g1
|
||||||
let g2: Int = try! await withTaskGroup(of: Int.self) { group in
|
let g2: Int = try! await withTaskGroup(of: Int.self) { group in
|
||||||
for i in 1...3 {
|
for i in 1...3 {
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
await Task.sleep(1_000_000_000)
|
await Task.sleep(1_000_000_000)
|
||||||
let c = Task.isCancelled
|
let c = Task.isCancelled
|
||||||
print("g1 task \(i) (cancelled: \(c))")
|
print("g1 task \(i) (cancelled: \(c))")
|
||||||
|
|||||||
@@ -21,20 +21,20 @@ func test_taskGroup_cancel_from_inside_child() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let result = await withTaskGroup(of: Int.self, returning: Int.self) { group in
|
let result = await withTaskGroup(of: Int.self, returning: Int.self) { group in
|
||||||
let firstAdded = await group.spawn { [group] in // must explicitly capture, as the task executes concurrently
|
let firstAdded = group.spawn { [group] in // must explicitly capture, as the task executes concurrently
|
||||||
group.cancelAll() // allowed
|
group.cancelAll() // allowed
|
||||||
print("first")
|
print("first")
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
print("firstAdded: \(firstAdded)") // CHECK: firstAdded: true
|
print("firstAdded: \(firstAdded.successfully)") // CHECK: firstAdded: true
|
||||||
|
|
||||||
let one = await group.next()
|
let one = await group.next()
|
||||||
|
|
||||||
let secondAdded = await group.spawn {
|
let secondAdded = group.spawn {
|
||||||
print("second")
|
print("second")
|
||||||
return 2
|
return 2
|
||||||
}
|
}
|
||||||
print("secondAdded: \(secondAdded)") // CHECK: secondAdded: false
|
print("secondAdded: \(secondAdded.successfully)") // CHECK: secondAdded: false
|
||||||
|
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|||||||
51
test/Concurrency/Runtime/async_taskgroup_cancel_handle.swift
Normal file
51
test/Concurrency/Runtime/async_taskgroup_cancel_handle.swift
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency %import-libdispatch -parse-as-library) | %FileCheck %s --dump-input always
|
||||||
|
|
||||||
|
// REQUIRES: executable_test
|
||||||
|
// REQUIRES: concurrency
|
||||||
|
// REQUIRES: libdispatch
|
||||||
|
|
||||||
|
import Dispatch
|
||||||
|
|
||||||
|
func asyncEcho(_ value: Int) async -> Int {
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
|
func test_taskGroup_cancel_then_add() async {
|
||||||
|
// CHECK: test_taskGroup_cancel_then_add
|
||||||
|
print("\(#function)")
|
||||||
|
let result: Int = await withTaskGroup(of: Int.self) { group in
|
||||||
|
|
||||||
|
let addedFirst = group.spawn {
|
||||||
|
while !Task.isCancelled {
|
||||||
|
await Task.sleep(50)
|
||||||
|
}
|
||||||
|
print("first done")
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
print("added first: \(addedFirst.successfully)") // CHECK: added first: true
|
||||||
|
|
||||||
|
print("cancel first") // CHECK: cancel first
|
||||||
|
addedFirst.handle?.cancel() // CHECK: first done
|
||||||
|
|
||||||
|
let one = await group.next()!
|
||||||
|
print("next first: \(one)") // CHECK: next first: 1
|
||||||
|
|
||||||
|
let addedSecond = group.spawn { 2 }
|
||||||
|
print("added second: \(addedSecond.successfully)") // CHECK: added second: true
|
||||||
|
|
||||||
|
let none = await group.next()!
|
||||||
|
print("next second: \(none)") // CHECK: next second: 2
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
print("result: \(result)") // CHECK: result: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@main struct Main {
|
||||||
|
static func main() async {
|
||||||
|
await test_taskGroup_cancel_then_add()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,9 +17,9 @@ func asyncEcho(_ value: Int) async -> Int {
|
|||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func test_taskGroup_cancel_parent_affects_group() async {
|
func test_taskGroup_cancel_parent_affects_group() async {
|
||||||
|
|
||||||
let x = spawnDetached {
|
let x = detach {
|
||||||
await withTaskGroup(of: Int.self, returning: Void.self) { group in
|
await withTaskGroup(of: Int.self, returning: Void.self) { group in
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
await Task.sleep(3_000_000_000)
|
await Task.sleep(3_000_000_000)
|
||||||
let c = Task.isCancelled
|
let c = Task.isCancelled
|
||||||
print("group task isCancelled: \(c)")
|
print("group task isCancelled: \(c)")
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ func test_taskGroup_cancel_then_add() async {
|
|||||||
print("\(#function)")
|
print("\(#function)")
|
||||||
let result: Int = await withTaskGroup(of: Int.self) { group in
|
let result: Int = await withTaskGroup(of: Int.self) { group in
|
||||||
|
|
||||||
let addedFirst = await group.spawn { 1 }
|
let addedFirst = group.spawn { 1 }
|
||||||
print("added first: \(addedFirst)") // CHECK: added first: true
|
print("added first: \(addedFirst.successfully)") // CHECK: added first: true
|
||||||
|
|
||||||
let one = await group.next()!
|
let one = await group.next()!
|
||||||
print("next first: \(one)") // CHECK: next first: 1
|
print("next first: \(one)") // CHECK: next first: 1
|
||||||
@@ -29,8 +29,8 @@ func test_taskGroup_cancel_then_add() async {
|
|||||||
group.cancelAll()
|
group.cancelAll()
|
||||||
print("cancelAll")
|
print("cancelAll")
|
||||||
|
|
||||||
let addedSecond = await group.spawn { 1 }
|
let addedSecond = group.spawn { 1 }
|
||||||
print("added second: \(addedSecond)") // CHECK: added second: false
|
print("added second: \(addedSecond.successfully)") // CHECK: added second: false
|
||||||
|
|
||||||
let none = await group.next()
|
let none = await group.next()
|
||||||
print("next second: \(none)") // CHECK: next second: nil
|
print("next second: \(none)") // CHECK: next second: nil
|
||||||
|
|||||||
@@ -14,32 +14,45 @@ func asyncEcho(_ value: Int) async -> Int {
|
|||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: this is a workaround since (A, B) today isn't inferred to be Sendable
|
||||||
|
// and causes an error, but should be a warning (this year at least)
|
||||||
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
|
struct SendableTuple2<A: Sendable, B: Sendable>: Sendable {
|
||||||
|
let first: A
|
||||||
|
let second: B
|
||||||
|
|
||||||
|
init(_ first: A, _ second: B) {
|
||||||
|
self.first = first
|
||||||
|
self.second = second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func test_taskGroup_cancel_then_completions() async {
|
func test_taskGroup_cancel_then_completions() async {
|
||||||
// CHECK: test_taskGroup_cancel_then_completions
|
// CHECK: test_taskGroup_cancel_then_completions
|
||||||
print("before \(#function)")
|
print("before \(#function)")
|
||||||
|
|
||||||
let result: Int = await withTaskGroup(of: (Int, Bool).self) { group in
|
let result: Int = await withTaskGroup(of: SendableTuple2<Int, Bool>.self) { group in
|
||||||
print("group cancelled: \(group.isCancelled)") // CHECK: group cancelled: false
|
print("group cancelled: \(group.isCancelled)") // CHECK: group cancelled: false
|
||||||
let addedFirst = await group.spawn {
|
let spawnedFirst = group.spawn {
|
||||||
print("start first")
|
print("start first")
|
||||||
await Task.sleep(1_000_000_000)
|
await Task.sleep(1_000_000_000)
|
||||||
print("done first")
|
print("done first")
|
||||||
return (1, Task.isCancelled)
|
return SendableTuple2(1, Task.isCancelled)
|
||||||
}
|
}
|
||||||
print("added first: \(addedFirst)") // CHECK: added first: true
|
print("spawned first: \(spawnedFirst.successfully)") // CHECK: spawned first: true
|
||||||
assert(addedFirst)
|
assert(spawnedFirst.successfully)
|
||||||
|
|
||||||
let addedSecond = await group.spawn {
|
let spawnedSecond = group.spawn {
|
||||||
print("start second")
|
print("start second")
|
||||||
await Task.sleep(3_000_000_000)
|
await Task.sleep(3_000_000_000)
|
||||||
print("done second")
|
print("done second")
|
||||||
return (2, Task.isCancelled)
|
return SendableTuple2(2, Task.isCancelled)
|
||||||
}
|
}
|
||||||
print("added second: \(addedSecond)") // CHECK: added second: true
|
print("spawned second: \(spawnedSecond.successfully)") // CHECK: spawned second: true
|
||||||
assert(addedSecond)
|
assert(spawnedSecond.successfully)
|
||||||
|
|
||||||
group.cancelAll() // FIXME: dont make it async
|
group.cancelAll()
|
||||||
print("cancelAll") // CHECK: cancelAll
|
print("cancelAll") // CHECK: cancelAll
|
||||||
|
|
||||||
// let outerCancelled = await outer // should not be cancelled
|
// let outerCancelled = await outer // should not be cancelled
|
||||||
@@ -47,13 +60,13 @@ func test_taskGroup_cancel_then_completions() async {
|
|||||||
// print("group cancelled: \(group.isCancelled)") // COM: CHECK: outer cancelled: false
|
// print("group cancelled: \(group.isCancelled)") // COM: CHECK: outer cancelled: false
|
||||||
|
|
||||||
let one = await group.next()
|
let one = await group.next()
|
||||||
print("first: \(one)") // CHECK: first: Optional((1,
|
print("first: \(one)") // CHECK: first: Optional(main.SendableTuple2<Swift.Int, Swift.Bool>(first: 1,
|
||||||
let two = await group.next()
|
let two = await group.next()
|
||||||
print("second: \(two)") // CHECK: second: Optional((2,
|
print("second: \(two)") // CHECK: second: Optional(main.SendableTuple2<Swift.Int, Swift.Bool>(first: 2,
|
||||||
let none = await group.next()
|
let none = await group.next()
|
||||||
print("none: \(none)") // CHECK: none: nil
|
print("none: \(none)") // CHECK: none: nil
|
||||||
|
|
||||||
return (one?.0 ?? 0) + (two?.0 ?? 0) + (none?.0 ?? 0)
|
return (one?.first ?? 0) + (two?.first ?? 0) + (none?.first ?? 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
print("result: \(result)") // CHECK: result: 3
|
print("result: \(result)") // CHECK: result: 3
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ func test_taskGroup_is_asyncSequence() async {
|
|||||||
|
|
||||||
let sum = await withTaskGroup(of: Int.self, returning: Int.self) { group in
|
let sum = await withTaskGroup(of: Int.self, returning: Int.self) { group in
|
||||||
for n in 1...10 {
|
for n in 1...10 {
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
print("add \(n)")
|
print("add \(n)")
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
@@ -38,7 +38,7 @@ func test_throwingTaskGroup_is_asyncSequence() async throws {
|
|||||||
|
|
||||||
let sum = try await withThrowingTaskGroup(of: Int.self, returning: Int.self) { group in
|
let sum = try await withThrowingTaskGroup(of: Int.self, returning: Int.self) { group in
|
||||||
for n in 1...10 {
|
for n in 1...10 {
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
print("add \(n)")
|
print("add \(n)")
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ func test_taskGroup_isEmpty() async {
|
|||||||
// CHECK: before add: isEmpty=true
|
// CHECK: before add: isEmpty=true
|
||||||
print("before add: isEmpty=\(group.isEmpty)")
|
print("before add: isEmpty=\(group.isEmpty)")
|
||||||
|
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
await Task.sleep(2_000_000_000)
|
await Task.sleep(2_000_000_000)
|
||||||
return await asyncEcho(1)
|
return await asyncEcho(1)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func test_skipCallingNext_butInvokeCancelAll() async {
|
|||||||
let result = try! await withTaskGroup(of: Int.self) { (group) async -> Int in
|
let result = try! await withTaskGroup(of: Int.self) { (group) async -> Int in
|
||||||
for n in numbers {
|
for n in numbers {
|
||||||
print("group.spawn { \(n) }")
|
print("group.spawn { \(n) }")
|
||||||
await group.spawn { [group] () async -> Int in
|
group.spawn { [group] () async -> Int in
|
||||||
await Task.sleep(1_000_000_000)
|
await Task.sleep(1_000_000_000)
|
||||||
print(" inside group.spawn { \(n) }")
|
print(" inside group.spawn { \(n) }")
|
||||||
print(" inside group.spawn { \(n) } (group cancelled: \(group.isCancelled))")
|
print(" inside group.spawn { \(n) } (group cancelled: \(group.isCancelled))")
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ func test_skipCallingNext() async {
|
|||||||
let result = try! await withTaskGroup(of: Int.self) { (group) async -> Int in
|
let result = try! await withTaskGroup(of: Int.self) { (group) async -> Int in
|
||||||
for n in numbers {
|
for n in numbers {
|
||||||
print("group.spawn { \(n) }")
|
print("group.spawn { \(n) }")
|
||||||
await group.spawn { () async -> Int in
|
group.spawn { () async -> Int in
|
||||||
await Task.sleep(1_000_000_000)
|
await Task.sleep(1_000_000_000)
|
||||||
let c = Task.isCancelled
|
let c = Task.isCancelled
|
||||||
print(" inside group.spawn { \(n) } (canceled: \(c))")
|
print(" inside group.spawn { \(n) } (canceled: \(c))")
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ func test_sum_nextOnCompleted() async {
|
|||||||
let sum = try! await withTaskGroup(of: Int.self) {
|
let sum = try! await withTaskGroup(of: Int.self) {
|
||||||
(group) async -> Int in
|
(group) async -> Int in
|
||||||
for n in numbers {
|
for n in numbers {
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
() async -> Int in
|
() async -> Int in
|
||||||
print(" complete group.spawn { \(n) }")
|
print(" complete group.spawn { \(n) }")
|
||||||
return n
|
return n
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ func test_sum_nextOnPending() async {
|
|||||||
|
|
||||||
let sum = try! await withTaskGroup(of: Int.self) { (group) async -> Int in
|
let sum = try! await withTaskGroup(of: Int.self) { (group) async -> Int in
|
||||||
for n in numbers {
|
for n in numbers {
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
let res = await completeSlowly(n: n)
|
let res = await completeSlowly(n: n)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ func boom() async throws -> Int {
|
|||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func test_taskGroup_throws() async {
|
func test_taskGroup_throws() async {
|
||||||
let got: Int = try await withThrowingTaskGroup(of: Int.self) { group in
|
let got: Int = try await withThrowingTaskGroup(of: Int.self) { group in
|
||||||
await group.spawn { try await boom() }
|
group.spawn { try await boom() }
|
||||||
|
|
||||||
do {
|
do {
|
||||||
while let r = try await group.next() {
|
while let r = try await group.next() {
|
||||||
@@ -34,7 +34,7 @@ func test_taskGroup_throws() async {
|
|||||||
let gc = group.isCancelled
|
let gc = group.isCancelled
|
||||||
print("group cancelled: \(gc)")
|
print("group cancelled: \(gc)")
|
||||||
|
|
||||||
await group.spawn { () async -> Int in
|
group.spawn { () async -> Int in
|
||||||
let c = Task.isCancelled
|
let c = Task.isCancelled
|
||||||
print("task 3 (cancelled: \(c))")
|
print("task 3 (cancelled: \(c))")
|
||||||
return 3
|
return 3
|
||||||
|
|||||||
@@ -20,9 +20,9 @@ func boom() async throws -> Int { throw Boom() }
|
|||||||
func test_taskGroup_throws_rethrows() async {
|
func test_taskGroup_throws_rethrows() async {
|
||||||
do {
|
do {
|
||||||
let got = try await withThrowingTaskGroup(of: Int.self, returning: Int.self) { group in
|
let got = try await withThrowingTaskGroup(of: Int.self, returning: Int.self) { group in
|
||||||
await group.spawn { await echo(1) }
|
group.spawn { await echo(1) }
|
||||||
await group.spawn { await echo(2) }
|
group.spawn { await echo(2) }
|
||||||
await group.spawn { try await boom() }
|
group.spawn { try await boom() }
|
||||||
|
|
||||||
do {
|
do {
|
||||||
while let r = try await group.next() {
|
while let r = try await group.next() {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ func testSimple(
|
|||||||
|
|
||||||
var completed = false
|
var completed = false
|
||||||
|
|
||||||
let taskHandle: Task.Handle<String, Error> = spawnDetached {
|
let taskHandle: Task.Handle<String, Error> = detach {
|
||||||
let greeting = await formGreeting(name: name)
|
let greeting = await formGreeting(name: name)
|
||||||
|
|
||||||
// If the intent is to test suspending, wait a bit so the second task
|
// If the intent is to test suspending, wait a bit so the second task
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ struct App {
|
|||||||
var n = 0
|
var n = 0
|
||||||
for _ in 1...NUM_TASKS {
|
for _ in 1...NUM_TASKS {
|
||||||
let c = Capture()
|
let c = Capture()
|
||||||
let r = spawnDetached {
|
let r = detach {
|
||||||
c.doSomething()
|
c.doSomething()
|
||||||
}
|
}
|
||||||
await r.get()
|
await r.get()
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ actor Container {
|
|||||||
for _ in 0..<n {
|
for _ in 0..<n {
|
||||||
let id = generation
|
let id = generation
|
||||||
generation += 1
|
generation += 1
|
||||||
let t = spawnDetached { [weak self] in
|
let t = detach { [weak self] in
|
||||||
let r = Runner()
|
let r = Runner()
|
||||||
await r.run()
|
await r.run()
|
||||||
await self?.remove(id)
|
await self?.remove(id)
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ func asyncFib(_ n: Int) async -> Int {
|
|||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|
||||||
let first = spawnDetached {
|
let first = detach {
|
||||||
await asyncFib(n - 2)
|
await asyncFib(n - 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
let second = spawnDetached {
|
let second = detach {
|
||||||
await asyncFib(n - 1)
|
await asyncFib(n - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ struct MyGlobalActor {
|
|||||||
// expected-error@+3{{actor-isolated var 'number' cannot be passed 'inout' to 'async' function call}}
|
// expected-error@+3{{actor-isolated var 'number' cannot be passed 'inout' to 'async' function call}}
|
||||||
// expected-error@+2{{var 'number' isolated to global actor 'MyGlobalActor' can not be used 'inout' from a non-isolated context}}
|
// expected-error@+2{{var 'number' isolated to global actor 'MyGlobalActor' can not be used 'inout' from a non-isolated context}}
|
||||||
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
|
if #available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *) {
|
||||||
let _ = spawnDetached { await { (_ foo: inout Int) async in foo += 1 }(&number) }
|
let _ = detach { await { (_ foo: inout Int) async in foo += 1 }(&number) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// attempt to pass global state owned by the global actor to another async function
|
// attempt to pass global state owned by the global actor to another async function
|
||||||
|
|||||||
@@ -706,7 +706,7 @@ class SomeClassWithInits {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func hasDetached() {
|
func hasDetached() {
|
||||||
spawnDetached {
|
detach {
|
||||||
// okay
|
// okay
|
||||||
await self.isolated() // expected-warning{{cannot use parameter 'self' with a non-sendable type 'SomeClassWithInits' from concurrently-executed code}}
|
await self.isolated() // expected-warning{{cannot use parameter 'self' with a non-sendable type 'SomeClassWithInits' from concurrently-executed code}}
|
||||||
self.isolated() // expected-warning{{cannot use parameter 'self' with a non-sendable type 'SomeClassWithInits' from concurrently-executed code}}
|
self.isolated() // expected-warning{{cannot use parameter 'self' with a non-sendable type 'SomeClassWithInits' from concurrently-executed code}}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ struct SomeFile: Sendable {
|
|||||||
|
|
||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func test_cancellation_withCancellationHandler(_ anything: Any) async -> PictureData {
|
func test_cancellation_withCancellationHandler(_ anything: Any) async -> PictureData {
|
||||||
let handle: Task.Handle<PictureData, Error> = spawnDetached {
|
let handle: Task.Handle<PictureData, Error> = detach {
|
||||||
let file = SomeFile()
|
let file = SomeFile()
|
||||||
|
|
||||||
return await Task.withCancellationHandler(
|
return await Task.withCancellationHandler(
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ func asyncThrowsOnCancel() async throws -> Int {
|
|||||||
|
|
||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func test_taskGroup_add() async throws -> Int {
|
func test_taskGroup_add() async throws -> Int {
|
||||||
try await withTaskGroup(of: Int.self) { group in
|
try await withThrowingTaskGroup(of: Int.self) { group in
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
await asyncFunc()
|
await asyncFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
await asyncFunc()
|
await asyncFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,10 +50,10 @@ func boom() async throws -> Int { throw Boom() }
|
|||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func first_allMustSucceed() async throws {
|
func first_allMustSucceed() async throws {
|
||||||
|
|
||||||
let first: Int = try await withTaskGroup(of: Int.self) { group in
|
let first: Int = try await withThrowingTaskGroup(of: Int.self) { group in
|
||||||
await group.spawn { await work() }
|
group.spawn { await work() }
|
||||||
await group.spawn { await work() }
|
group.spawn { await work() }
|
||||||
await group.spawn { try await boom() }
|
group.spawn { try await boom() }
|
||||||
|
|
||||||
if let first = try await group.next() {
|
if let first = try await group.next() {
|
||||||
return first
|
return first
|
||||||
@@ -71,10 +71,10 @@ func first_ignoreFailures() async throws {
|
|||||||
@Sendable func work() async -> Int { 42 }
|
@Sendable func work() async -> Int { 42 }
|
||||||
@Sendable func boom() async throws -> Int { throw Boom() }
|
@Sendable func boom() async throws -> Int { throw Boom() }
|
||||||
|
|
||||||
let first: Int = try await withTaskGroup(of: Int.self) { group in
|
let first: Int = try await withThrowingTaskGroup(of: Int.self) { group in
|
||||||
await group.spawn { await work() }
|
group.spawn { await work() }
|
||||||
await group.spawn { await work() }
|
group.spawn { await work() }
|
||||||
await group.spawn {
|
group.spawn {
|
||||||
do {
|
do {
|
||||||
return try await boom()
|
return try await boom()
|
||||||
} catch {
|
} catch {
|
||||||
@@ -119,9 +119,9 @@ func test_taskGroup_quorum_thenCancel() async {
|
|||||||
///
|
///
|
||||||
/// - Returns: `true` iff `N/2 + 1` followers return `.yay`, `false` otherwise.
|
/// - Returns: `true` iff `N/2 + 1` followers return `.yay`, `false` otherwise.
|
||||||
func gatherQuorum(followers: [Follower]) async -> Bool {
|
func gatherQuorum(followers: [Follower]) async -> Bool {
|
||||||
try! await withTaskGroup(of: Vote.self) { group in
|
try! await withThrowingTaskGroup(of: Vote.self) { group in
|
||||||
for follower in followers {
|
for follower in followers {
|
||||||
await group.spawn { try await follower.vote() }
|
group.spawn { try await follower.vote() }
|
||||||
}
|
}
|
||||||
|
|
||||||
defer {
|
defer {
|
||||||
@@ -154,7 +154,19 @@ func test_taskGroup_quorum_thenCancel() async {
|
|||||||
_ = await gatherQuorum(followers: [Follower("A"), Follower("B"), Follower("C")])
|
_ = await gatherQuorum(followers: [Follower("A"), Follower("B"), Follower("C")])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: this is a workaround since (A, B) today isn't inferred to be Sendable
|
||||||
|
// and causes an error, but should be a warning (this year at least)
|
||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
|
struct SendableTuple2<A: Sendable, B: Sendable>: Sendable {
|
||||||
|
let first: A
|
||||||
|
let second: B
|
||||||
|
|
||||||
|
init(_ first: A, _ second: B) {
|
||||||
|
self.first = first
|
||||||
|
self.second = second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension Collection where Self: Sendable, Element: Sendable, Self.Index: Sendable {
|
extension Collection where Self: Sendable, Element: Sendable, Self.Index: Sendable {
|
||||||
|
|
||||||
/// Just another example of how one might use task groups.
|
/// Just another example of how one might use task groups.
|
||||||
@@ -171,7 +183,7 @@ extension Collection where Self: Sendable, Element: Sendable, Self.Index: Sendab
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return try await withTaskGroup(of: (Int, T).self) { group in
|
return try await withThrowingTaskGroup(of: SendableTuple2<Int, T>.self) { group in
|
||||||
var result = ContiguousArray<T>()
|
var result = ContiguousArray<T>()
|
||||||
result.reserveCapacity(n)
|
result.reserveCapacity(n)
|
||||||
|
|
||||||
@@ -179,9 +191,9 @@ extension Collection where Self: Sendable, Element: Sendable, Self.Index: Sendab
|
|||||||
var submitted = 0
|
var submitted = 0
|
||||||
|
|
||||||
func submitNext() async throws {
|
func submitNext() async throws {
|
||||||
await group.spawn { [submitted,i] in
|
group.spawn { [submitted,i] in
|
||||||
let value = try await transform(self[i])
|
let value = try await transform(self[i])
|
||||||
return (submitted, value)
|
return SendableTuple2(submitted, value)
|
||||||
}
|
}
|
||||||
submitted += 1
|
submitted += 1
|
||||||
formIndex(after: &i)
|
formIndex(after: &i)
|
||||||
@@ -192,7 +204,9 @@ extension Collection where Self: Sendable, Element: Sendable, Self.Index: Sendab
|
|||||||
try await submitNext()
|
try await submitNext()
|
||||||
}
|
}
|
||||||
|
|
||||||
while let (index, taskResult) = try await group.next() {
|
while let tuple = try await group.next() {
|
||||||
|
let index = tuple.first
|
||||||
|
let taskResult = tuple.second
|
||||||
result[index] = taskResult
|
result[index] = taskResult
|
||||||
|
|
||||||
try Task.checkCancellation()
|
try Task.checkCancellation()
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ func test_unsafeThrowingContinuations() async throws {
|
|||||||
|
|
||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func test_detached() async throws {
|
func test_detached() async throws {
|
||||||
let handle = spawnDetached() {
|
let handle = detach() {
|
||||||
await someAsyncFunc() // able to call async functions
|
await someAsyncFunc() // able to call async functions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ func test_detached() async throws {
|
|||||||
|
|
||||||
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
|
||||||
func test_detached_throwing() async -> String {
|
func test_detached_throwing() async -> String {
|
||||||
let handle: Task.Handle<String, Error> = spawnDetached() {
|
let handle: Task.Handle<String, Error> = detach() {
|
||||||
try await someThrowingAsyncFunc() // able to call async functions
|
try await someThrowingAsyncFunc() // able to call async functions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ extension Collection {
|
|||||||
return await try withTaskGroup(of: Element.self) { group in
|
return await try withTaskGroup(of: Element.self) { group in
|
||||||
var i = self.startIndex
|
var i = self.startIndex
|
||||||
func doit() async throws {
|
func doit() async throws {
|
||||||
await group.spawn { [i] in
|
group.spawn { [i] in
|
||||||
return self[i]
|
return self[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ func runTest(numCounters: Int, numWorkers: Int, numIterations: Int) async {
|
|||||||
var workers: [Task.Handle<Void, Error>] = []
|
var workers: [Task.Handle<Void, Error>] = []
|
||||||
for i in 0..<numWorkers {
|
for i in 0..<numWorkers {
|
||||||
workers.append(
|
workers.append(
|
||||||
spawnDetached { [counters] in
|
detach { [counters] in
|
||||||
usleep(UInt32.random(in: 0..<100) * 1000)
|
usleep(UInt32.random(in: 0..<100) * 1000)
|
||||||
await worker(identity: i, counters: counters, numIterations: numIterations)
|
await worker(identity: i, counters: counters, numIterations: numIterations)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ func testSimple(
|
|||||||
|
|
||||||
var completed = false
|
var completed = false
|
||||||
|
|
||||||
let taskHandle: Task.Handle<String, Error> = spawnDetached {
|
let taskHandle: Task.Handle<String, Error> = detach {
|
||||||
let greeting = await formGreeting(name: name)
|
let greeting = await formGreeting(name: name)
|
||||||
|
|
||||||
// If the intent is to test suspending, wait a bit so the second task
|
// If the intent is to test suspending, wait a bit so the second task
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ func runTest(numCounters: Int, numWorkers: Int, numIterations: Int) async {
|
|||||||
var workers: [Task.Handle<Void, Error>] = []
|
var workers: [Task.Handle<Void, Error>] = []
|
||||||
for i in 0..<numWorkers {
|
for i in 0..<numWorkers {
|
||||||
workers.append(
|
workers.append(
|
||||||
spawnDetached { [counters] in
|
detach { [counters] in
|
||||||
await worker(identity: i, counters: counters, numIterations: numIterations)
|
await worker(identity: i, counters: counters, numIterations: numIterations)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user