Task { // do something... }
Task
內// Task { await something() // }
digraph Task {
rankdir = LR;
suspended -> running [label = "schedule"]
running -> completed [label = "return/throw"]
running -> suspended [label = "await"]
}
await 是潛在
暫停點
digraph Task {
rankdir = LR;
suspended -> running [label = "schedule"]
running -> completed [label = "return/throw"]
running -> suspended [label = "await"]
running -> running [label = "await"]
}
func main() { all() } func all() { a1() a2() a3() }
gantt
title all
dateFormat YYYY-MM-DD ss
axisFormat %S
section Main Thread
a2 :a2, after a1, 2s
a3 :a3, after a2, 2s
a1 :a1, 2000-01-01 00, 2s
all :all, 2000-01-01 00, 6s
main :main, 2000-01-01 00, 6s
func all() async { a1() await a2() a3() }
可在任意 thread 切換執行(executor)
gantt
title all
dateFormat YYYY-MM-DD ss
axisFormat %S
section Global Executor1
a1 :a1, 2000-01-01 00, 2s
section Global Executor2
a2 :a2, after a1, 2s
section Global Executor3
a3 :a3, after a2, 2s
await 是潛在暫停點,也有機會不暫停
gantt
title all
dateFormat YYYY-MM-DD ss
axisFormat %S
section Global Executor
all :2000-01-01 00, 6s
actor A { let b = B() func all() async { a1() await b.a2() a3() } } actor B { func a2() {} }
每個 actor 有各自的 executor
gantt
title all
dateFormat YYYY-MM-DD ss
axisFormat %S
section A
a1 :a1, 2000-01-01 00, 2s
a3 :a3, after a2, 2s
section B
a2 :a2, after a1, 2s
Task Cancellation
只標記 Task 為取消
task.cancel() // task.isCancelled = true
Task { while !Task.isCancelled { // your works } return }
func doSomething() async { while !Task.isCancelled { // your works } return }
Task { try Task.checkCancellation() }
cancel 只能停止
預設情況下
Task
排程到default global concurrent executor
併發執行
actor SomeActor {}
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public protocol Actor : AnyObject, Sendable { nonisolated var unownedExecutor: UnownedSerialExecutor { get } } @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) public protocol GlobalActor { // ... static var sharedUnownedExecutor: UnownedSerialExecutor { get } }
需要自行實作 cancel 流程
在 Task 被取消時,立即呼叫 cancel handler
func download(url: URL) async throws -> Data? { var urlSessionTask: URLSessionTask? return try withTaskCancellationHandler { return try await withUnsafeThrowingContinuation { continuation in urlSessionTask = URLSession.shared.dataTask(with: url) { data, _, error in if let error = error { // Ideally translate NSURLErrorCancelled to CancellationError here continuation.resume(throwing: error) } else { continuation.resume(returning: data) } } urlSessionTask?.resume() } } onCancel: { urlSessionTask?.cancel() // runs immediately when cancelled } }
public var data: Observable<Data> { return Observable.create { observer -> Disposable in self.dataRequest.responseData { (res) in do { try observer.onNext(res.result.get()) observer.onCompleted() } catch { observer.onError(error) } return Disposables.create { self.dataRequest.cancel() } } } }
public var data: Data { get async throws { return try withTaskCancellationHandler { return try await withCheckedThrowingContinuation { continuation in self.dataRequest.responseData { (res) in do { let value = try res.result.get() continuation.resume(returning: value) } catch { continuation.resume(throwing: error) } } } } onCancel: { self.dataRequest.cancel() } } }
Task { // ... }
TaskPriority
func xxx() async { Task { // 繼承 xxx `TaskPriority` } }
TaskLocal
values
@TaskLocal static var traceID: TraceID? print("traceID: \(traceID)") // traceID: nil $traceID.withValue(1234) { // bind the value print("traceID: \(traceID)") // traceID: 1234 }
actor One { var counter = 1 // mutable properties var notSendable = NotSendable() // non-sendable values func xxx() { Task { // 繼承 actor 的 Execution Context // 此 task 排程到 actor's executer // closure 變成 `actor-isolated` // 可以存取 actor 的私有狀態(`actor-isolated` state) // mutable properties // non-sendable values counter += 1 notSendable.xxx() } } }
func xxx() { // Main Thread Task { // 諮詢 `runtime` 並推導出最可能的 `TaskPriority` // 例如問 `current thread priority` // 排程到 `global concurrent executor` } }
傳遞給 Task init
的閉包將隱式繼承 actor
execution context
和形成閉包的上下文的隔離
func notOnActor(_: @Sendable () async -> Void) { } actor A { func f() { notOnActor { await g() // must call g asynchronously, because it's a @Sendable closure } Task { g() // okay to call g synchronously, even though it's @Sendable } } func g() { } }
Implicit "self"
self
的意圖,是為了提醒開發者 capture self
會有潛在 reference cycle
Task init
的閉包會被立刻執行,而且只會在內部 reference,因此 explicit self
func acceptEscaping(_: @escaping () -> Void) { } class C { var counter: Int = 0 func f() { acceptEscaping { counter = counter + 1 // error: must use "self." because the closure escapes } Task { counter = counter + 1 // okay: implicit "self" is allowed here } } }
Task
的 Priority
是用來協助 Executor
做出 排程決策(scheduling decisions)
high
medium
low
userInitiated
utility
background
避免使用特定平台相關術語
採用較通俗的術語
在某些情境下
Task
的Pirority
必須被提升
以避免Priority Inversion
高優先權 Task B 同等
的優先權
執行Parent - Child Task
func chopVegetables() async throws -> [Vegetable] { ... } func marinateMeat() async -> Meat { ... } func preheatOven(temperature: Double) async throws -> Oven { ... } // ... func makeDinner() async throws -> Meal { let veggies = try await chopVegetables() let meat = await marinateMeat() let oven = try await preheatOven(temperature: 350) let dish = Dish(ingredients: [veggies, meat]) return try await oven.cook(dish, duration: .hours(3)) }
gantt
title Dinner
dateFormat YYYY-MM-DD ss
axisFormat %S
section 廚房
Vegetables :Vegetables, 2000-01-01 00, 2s
Meat :Meat, after Vegetables, 4s
Oven :Oven, after Meat, 6s
Dish :Dish, after Oven, 1s
Dinner :after Dish, 3s
@available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) @inlinable public func withTaskGroup<ChildTaskResult, GroupResult>( of childTaskResultType: ChildTaskResult.Type, returning returnType: GroupResult.Type = GroupResult.self, body: (inout TaskGroup<ChildTaskResult>) async -> GroupResult ) async -> GroupResult @available(macOS 10.15, iOS 13.0, watchOS 6.0, tvOS 13.0, *) @inlinable public func withThrowingTaskGroup<ChildTaskResult, GroupResult>( of childTaskResultType: ChildTaskResult.Type, returning returnType: GroupResult.Type = GroupResult.self, body: (inout ThrowingTaskGroup<ChildTaskResult, Error>) async throws -> GroupResult ) async rethrows -> GroupResult
@frozen public struct TaskGroup<ChildTaskResult> { public mutating func addTask( priority: TaskPriority? = nil, operation: @escaping @Sendable () async -> ChildTaskResult ) public mutating func addTaskUnlessCancelled( priority: TaskPriority? = nil, operation: @escaping @Sendable () async -> ChildTaskResult ) -> Bool }
// addTaskUnlessCancelled -> false // 則不加入新的 child task
for await value in group { // ... }
// 等待所有 child task public mutating func waitForAll() async // 有沒有未執行(pending) task public var isEmpty: Bool { get }
// group.isCancelled = true public func cancelAll() public var isCancelled: Bool { get }
func makeDinner() async throws -> Meal { // Prepare some variables to receive results from our concurrent child tasks var veggies: [Vegetable]? var meat: Meat? var oven: Oven? enum CookingStep { case veggies([Vegetable]) case meat(Meat) case oven(Oven) } // Create a task group to scope the lifetime of our three child tasks try await withThrowingTaskGroup(of: CookingStep.self) { group in group.addTask { try await .veggies(chopVegetables()) } group.addTask { await .meat(marinateMeat()) } group.addTask { try await .oven(preheatOven(temperature: 350)) } for try await finishedStep in group { switch finishedStep { case .veggies(let v): veggies = v case .meat(let m): meat = m case .oven(let o): oven = o } } } // If execution resumes normally after `withTaskGroup`, then we can assume // that all child tasks added to the group completed successfully. That means // we can confidently force-unwrap the variables containing the child task // results here. let dish = Dish(ingredients: [veggies!, meat!]) return try await oven!.cook(dish, duration: .hours(3)) }
gantt
title Dinner
dateFormat YYYY-MM-DD ss
axisFormat %S
section 廚房
Vegetables :Vegetables, 2000-01-01 00, 2s
Meat :Meat, 2000-01-01 00, 4s
Oven :Oven, 2000-01-01 00, 6s
Dish :Dish, after Oven, 1s
Dinner :after Dish, 3s
let x = await xxx() let y = await yyy()
async let x = xxx() async let y = yyy() await (x, y)
func chopVegetables() async throws -> [Vegetable] { ... } func marinateMeat() async -> Meat { ... } func preheatOven(temperature: Double) async throws -> Oven { ... } // ... func makeDinner() async throws -> Meal { async let veggies = chopVegetables() async let meat = marinateMeat() async let oven = preheatOven(temperature: 350) let dish = await Dish(ingredients: [veggies, meat]) return try await oven.cook(dish, duration: .hours(3)) }
gantt
title Dinner
dateFormat YYYY-MM-DD ss
axisFormat %S
section 廚房
Vegetables :Vegetables, 2000-01-01 00, 2s
Meat :Meat, 2000-01-01 00, 4s
Dish :Dish, after Meat, 1s
Oven :Oven, 2000-01-01 00, 6s
Dinner :after Oven, 3s
go
執行多久?
func go() async { async let f = fast() // 1s async let s = slow() // 3s // implicitly: cancels f // implicitly: cancels s // implicitly: await f // implicitly: await s return "nevermind..." }
async let
in closure不能被 @escaping
capture
目前似乎不支援
隱式: 取消 -> await -> 丟棄 error
func go() async -> String { async let f = fire() // f.cancel() // try? await f return "go" }
當 Parent Task
被取消,後續建立的 Child Task
也會被標記為 取消狀態
Unstructured tasks are not able to utilize some of the optimization techniques wrt.
限制在 Parent Task 內
group.add { sleep(1) ;return 1} group.add { sleep(100);return 100} return try await group.next() // return 1
gantt
title group
dateFormat YYYY-MM-DD ss
axisFormat %S
section Child
child1 :c1, 2000-01-01 00, 1s
child2 :c2, 2000-01-01 00, 10s
section Parent
Parent :p1, 2000-01-01 00, 1s
Parent Task 必須等待所有 Child Task 執行完畢
X
gantt
title group
dateFormat YYYY-MM-DD ss
axisFormat %S
section Child
child1 :c1, 2000-01-01 00, 1s
child2 :c2, 2000-01-01 00, 10s
section Parent
Parent :p1, 2000-01-01 00, 1s
O
gantt
title group
dateFormat YYYY-MM-DD ss
axisFormat %S
section Child
child1 :c1, 2000-01-01 00, 1s
child2 :c2, 2000-01-01 00, 10s
section Parent
Parent :p1, 2000-01-01 00, 10s
group.add { sleep(1) ;return 1} group.add { sleep(100);return 100} // group.waitForAll() return try await group.next() // 結果 // sleep 100 秒 // return 1 // // 等待所有 child task 結束 // 才能 return
Child Task 可以將錯誤往上拋給 Parent Task
do catch
try
拋出 error
cancel task,只有支援你有寫取消流程
cancel task,只有支援你有寫取消流程
cancel task,只有支援你有寫取消流程
無法被 cancel
async let
TaskGroup
async let
無法對 array
使用for i in items.indices { group.async { (i, await f(items[i])) } } async let f0 = f(items[0]) async let f1 = f(items[1])
TaskGroup
可按照 completion order
取得結果
async let
return 時
會隱式 cancel
stevapple 校稿, 流程等等建議