Skip to content

Commit 343f746

Browse files
authored
Merge pull request #34475 from ktoso/wip-tasks-followup
[Concurrency] Task namespace, priority and always throwing Task.Handle.get
2 parents 424802f + 107bc27 commit 343f746

File tree

2 files changed

+70
-38
lines changed

2 files changed

+70
-38
lines changed

stdlib/public/Concurrency/Task.swift

Lines changed: 35 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -18,32 +18,31 @@ import Swift
1818
/// An asynchronous task (just "Task" hereafter) is the analogue of a thread for
1919
/// asynchronous functions. All asynchronous functions run as part of some task.
2020
///
21+
/// A task can only be interacted with by code running "in" the task,
22+
/// by invoking the appropriate context sensitive static functions which operate
23+
/// on the "current" task. Because all such functions are `async` they can only
24+
/// be invoked as part of an existing task, and therefore are guaranteed to be
25+
/// effective.
26+
///
2127
/// A task's execution can be seen as a series of periods where the task was
2228
/// running. Each such period ends at a suspension point or -- finally -- the
2329
/// completion of the task.
2430
///
2531
/// These partial periods towards the task's completion are `PartialAsyncTask`.
2632
/// Partial tasks are generally not interacted with by end-users directly,
2733
/// unless implementing a scheduler.
28-
public struct Task {
34+
public enum Task {
2935
}
3036

31-
// ==== Current Task -----------------------------------------------------------
37+
// ==== Task Priority ----------------------------------------------------------
3238

3339
extension Task {
34-
/// Returns the currently executing `Task`.
35-
///
36-
/// As invoking this function is only possible from an asynchronous context
37-
/// it is always able to return the current `Task` in which we are currently
38-
/// running.
39-
public static func current() async -> Task {
40-
fatalError("\(#function) not implemented yet.") // TODO: needs a built-in function
41-
}
42-
}
4340

44-
// ==== Task Priority ----------------------------------------------------------
41+
/// Returns the current task's priority.
42+
public static func currentPriority() async -> Priority {
43+
fatalError("\(#function) not implemented yet.")
44+
}
4545

46-
extension Task {
4746
/// Task priority may inform decisions an `Executor` makes about how and when
4847
/// to schedule tasks submitted to it.
4948
///
@@ -61,7 +60,7 @@ extension Task {
6160
/// as they are "detached" from their parent tasks after all.
6261
///
6362
/// ### Priority elevation
64-
/// In some situations the priority of a task must be elevated ("raised"):
63+
/// In some situations the priority of a task must be elevated (or "escalated", "raised"):
6564
///
6665
/// - if a `Task` running on behalf of an actor, and a new higher-priority
6766
/// task is enqueued to the actor, its current task must be temporarily
@@ -99,7 +98,7 @@ extension Task {
9998
/// i.e. the task will run regardless of the handle still being present or not.
10099
/// Dropping a handle however means losing the ability to await on the task's result
101100
/// and losing the ability to cancel it.
102-
public final class Handle<Success, Failure: Error> {
101+
public final class Handle<Success> {
103102
/// Wait for the task to complete, returning (or throwing) its result.
104103
///
105104
/// ### Priority
@@ -108,7 +107,12 @@ extension Task {
108107
/// creating the task with the "right" priority to in the first place.
109108
///
110109
/// ### Cancellation
111-
/// If the awaited on task gets cancelled the `get()` will throw a cancellation error.
110+
/// If the awaited on task gets cancelled externally the `get()` will throw
111+
/// a cancellation error.
112+
///
113+
/// If the task gets cancelled internally, e.g. by checking for cancellation
114+
/// and throwing a specific error or using `checkCancellation` the error
115+
/// thrown out of the task will be re-thrown here.
112116
public func get() async throws -> Success {
113117
fatalError("\(#function) not implemented yet.")
114118
}
@@ -148,21 +152,21 @@ extension Task {
148152
///
149153
/// Canceling a task must be performed explicitly via `handle.cancel()`.
150154
///
151-
/// - Parameters:
152-
/// - priority: priority of the task TODO: reword and define more explicitly once we have priorities well-defined
153-
/// - operation:
154-
/// - Returns: handle to the task, allowing to `await handle.get()` on the
155-
/// tasks result or `cancel` it.
156-
///
157155
/// - Note: it is generally preferable to use child tasks rather than detached
158156
/// tasks. Child tasks automatically carry priorities, task-local state,
159157
/// deadlines and have other benefits resulting from the structured
160158
/// concurrency concepts that they model. Consider using detached tasks only
161159
/// when strictly necessary and impossible to model operations otherwise.
160+
///
161+
/// - Parameters:
162+
/// - priority: priority of the task TODO: reword and define more explicitly once we have priorities well-defined
163+
/// - operation: the operation to execute
164+
/// - Returns: handle to the task, allowing to `await handle.get()` on the
165+
/// tasks result or `cancel` it.
162166
public static func runDetached<T>(
163167
priority: Priority = .default,
164168
operation: () async -> T
165-
) -> Handle<T, Never> {
169+
) -> Handle<T> {
166170
fatalError("\(#function) not implemented yet.")
167171
}
168172

@@ -184,22 +188,22 @@ extension Task {
184188
///
185189
/// Canceling a task must be performed explicitly via `handle.cancel()`.
186190
///
187-
/// - Parameters:
188-
/// - priority: priority of the task TODO: reword and define more explicitly once we have priorities well-defined
189-
/// - operation:
190-
/// - Returns: handle to the task, allowing to `await handle.get()` on the
191-
/// tasks result or `cancel` it. If the operation fails the handle will
192-
/// throw the error the operation has thrown when awaited on.
193-
///
194191
/// - Note: it is generally preferable to use child tasks rather than detached
195192
/// tasks. Child tasks automatically carry priorities, task-local state,
196193
/// deadlines and have other benefits resulting from the structured
197194
/// concurrency concepts that they model. Consider using detached tasks only
198195
/// when strictly necessary and impossible to model operations otherwise.
196+
///
197+
/// - Parameters:
198+
/// - priority: priority of the task TODO: reword and define more explicitly once we have priorities well-defined
199+
/// - operation: the operation to execute
200+
/// - Returns: handle to the task, allowing to `await handle.get()` on the
201+
/// tasks result or `cancel` it. If the operation fails the handle will
202+
/// throw the error the operation has thrown when awaited on.
199203
public static func runDetached<T>(
200204
priority: Priority = .default,
201205
operation: () async throws -> T
202-
) -> Handle<T, Error> {
206+
) -> Handle<T> {
203207
fatalError("\(#function) not implemented yet.")
204208
}
205209
}

test/Concurrency/async_tasks.swift

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,40 @@ func someAsyncFunc() async -> String { "" }
66
struct MyError: Error {}
77
func someThrowingAsyncFunc() async throws -> String { throw MyError() }
88

9+
// ==== Unsafe Continuations ---------------------------------------------------
10+
11+
struct Vegetable {}
12+
13+
func buyVegetables(
14+
shoppingList: [String],
15+
// a) if all veggies were in store, this is invoked *exactly-once*
16+
onGotAllVegetables: ([Vegetable]) -> (),
17+
18+
// b) if not all veggies were in store, invoked one by one (one or more times)
19+
onGotVegetable: (Vegetable) -> (),
20+
// b) if at least one onGotVegetable was called *exactly-once*
21+
// this is invoked once no more veggies will be emitted
22+
onNoMoreVegetables: () -> (),
23+
// c) if no veggies _at all_ were available, this is invoked *exactly once*
24+
onNoVegetablesInStore: (Error) -> ()
25+
) {}
26+
27+
// returns 1 or more vegetables or throws an error
28+
func buyVegetables(shoppingList: [String]) async throws -> [Vegetable] {
29+
await try Task.withUnsafeThrowingContinuation { continuation in
30+
var veggies: [Vegetable] = []
31+
32+
buyVegetables(
33+
shoppingList: shoppingList,
34+
onGotAllVegetables: { veggies in continuation.resume(returning: veggies) },
35+
onGotVegetable: { v in veggies.append(v) },
36+
onNoMoreVegetables: { continuation.resume(returning: veggies) },
37+
onNoVegetablesInStore: { error in continuation.resume(throwing: error) }
38+
)
39+
}
40+
}
41+
42+
943
func test_unsafeContinuations() async {
1044
// the closure should not allow async operations;
1145
// after all: if you have async code, just call it directly, without the unsafe continuation
@@ -43,7 +77,7 @@ func test_detached() async throws {
4377
}
4478

4579
func test_detached_throwing() async -> String {
46-
let handle: Task.Handle<String, Error> = Task.runDetached() {
80+
let handle: Task.Handle<String> = Task.runDetached() {
4781
await try someThrowingAsyncFunc() // able to call async functions
4882
}
4983

@@ -53,9 +87,3 @@ func test_detached_throwing() async -> String {
5387
print("caught: \(error)")
5488
}
5589
}
56-
57-
// ==== Current Task -----------------------------------------------------------
58-
59-
func test_current_task() async {
60-
_ = await Task.current() // yay, we know "in" what task we're executing
61-
}

0 commit comments

Comments
 (0)