-
Notifications
You must be signed in to change notification settings - Fork 138
/
TaskSelect.swift
78 lines (73 loc) · 2.39 KB
/
TaskSelect.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Async Algorithms open source project
//
// Copyright (c) 2022 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//
struct TaskSelectState<Success: Sendable, Failure: Error>: Sendable {
var complete = false
var tasks: [Task<Success, Failure>]? = []
mutating func add(_ task: Task<Success, Failure>) -> Task<Success, Failure>? {
if var tasks = tasks {
tasks.append(task)
self.tasks = tasks
return nil
} else {
return task
}
}
}
extension Task {
/// Determine the first task to complete of a sequence of tasks.
///
/// - Parameters:
/// - tasks: The running tasks to obtain a result from
/// - Returns: The first task to complete from the running tasks
public static func select<Tasks: Sequence & Sendable>(
_ tasks: Tasks
) async -> Task<Success, Failure>
where Tasks.Element == Task<Success, Failure> {
let state = ManagedCriticalState(TaskSelectState<Success, Failure>())
return await withTaskCancellationHandler {
let tasks = state.withCriticalRegion { state -> [Task<Success, Failure>] in
defer { state.tasks = nil }
return state.tasks ?? []
}
for task in tasks {
task.cancel()
}
} operation: {
await withUnsafeContinuation { continuation in
for task in tasks {
Task<Void, Never> {
_ = await task.result
let winner = state.withCriticalRegion { state -> Bool in
defer { state.complete = true }
return !state.complete
}
if winner {
continuation.resume(returning: task)
}
}
state.withCriticalRegion { state in
state.add(task)
}?.cancel()
}
}
}
}
/// Determine the first task to complete of a list of tasks.
///
/// - Parameters:
/// - tasks: The running tasks to obtain a result from
/// - Returns: The first task to complete from the running tasks
public static func select(
_ tasks: Task<Success, Failure>...
) async -> Task<Success, Failure> {
await select(tasks)
}
}