Skip to content

Instantly share code, notes, and snippets.

@mbrandonw
Created January 18, 2023 17:07
Show Gist options
  • Save mbrandonw/92064c19c7b391acab3d69f65a5323be to your computer and use it in GitHub Desktop.
Save mbrandonw/92064c19c7b391acab3d69f65a5323be to your computer and use it in GitHub Desktop.
// A fix for https://github.com/AvdLee/TaskGroupsResultBuilder/blob/main/TaskGroups/TaskResultBuilder.swift
// to handle cancellation.
@resultBuilder
struct TaskBuilder {
static func buildExpression<Success: Sendable>(_ task: Task<Success, Never>) -> [Task<Success, Never>] {
[task]
}
static func buildBlock<Success: Sendable>(_ tasks: [Task<Success, Never>]...) -> [Task<Success, Never>] {
tasks.flatMap { $0 }
}
static func buildFinalResult<Success: Sendable>(_ tasks: [Task<Success, Never>]) -> Task<[Success], Never> {
Task {
await withTaskGroup(of: Success.self, returning: [Success].self) { taskGroup in
tasks.forEach { task in
taskGroup.addTask {
await withTaskCancellationHandler { // 👈
await task.value
} onCancel: {
task.cancel()
}
}
}
return await taskGroup.reduce(into: [Success]()) { partialResult, name in
partialResult.append(name)
}
}
}
}
}
func withTaskGroup<Success: Sendable>(@TaskBuilder builder: () -> Task<[Success], Never>) async -> [Success] {
let task = builder()
return await withTaskCancellationHandler { // 👈
await task.value
} onCancel: {
task.cancel()
}
}
// Test to confirm:
func testTaskGroupCancellation() async throws {
let task = Task {
await withTaskGroup {
Task {
do {
try await Task.sleep(for: .seconds(1))
XCTFail("🛑 This should not be executed")
} catch {}
}
}
}
try await Task.sleep(for: .milliseconds(100))
task.cancel()
try await Task.sleep(for: .seconds(1))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment