WWDC会话超越 struct 化并发的基础知识

11:56 - Kitchen Service

func run() async throws {
    try await withThrowingTaskGroup(of: Void.self) { group in
        for cook in staff.keys {
            group.addTask { try await cook.handleShift() }
        }

        group.addTask {
            // keep the restaurant going until closing time
            try await Task.sleep(for: shiftDuration)
        }

        try await group.next()
        // cancel all ongoing shifts
        group.cancelAll()
    }
}

教练解释说,一旦厨师开始工作,我们就会启动计时器.计时器结束后,我们取消所有正在进行的轮班.但await group.next()只等待下一个任务,这可能不是计时器.如果一名厨师下班,小组也会取消所有任务.我是错过了什么还是这个例子是错误的?

推荐答案

考虑您在问题中包含的例子:

func run() async throws {
    try await withThrowingTaskGroup(of: Void.self) { group in
        for cook in staff.keys {
            group.addTask { try await cook.handleShift() }
        }

        group.addTask {
            // keep the restaurant going until closing time
            try await Task.sleep(for: shiftDuration)
        }

        try await group.next()
        // cancel all ongoing shifts
        group.cancelAll()
    }
}

您的解释是正确的,小组的任何一项任务首先完成,无论是"轮班结束"超时任务,还是任何一项(!)在handleShift个电话中.只有当您假设handleShift个呼叫都不会在shiftDuration消失之前完成时,这才有意义.但做出任何此类假设都是轻率的.(此外,任何真正的企业都会在一名员工失业后立即关闭办公室.哈哈.)

恕我直言,这是一个非常糟糕的例子,我不会因此而失眠.你的判断是正确的.


如果我想要run来(a)让所有厨师完成工作;和(b)取消轮班结束时任何未完成的工作,我个人不会将"轮班结束"纳入任务组.通常,这种"超时"模式将用于非 struct 化并发,一个任务用于主任务(即,所有厨师的工作),另一个是"超时"(即,轮班结束).由于我们使用的是非 struct 化并发,因此我们将用withTaskCancellationHandler手动传播取消:

func run() async throws {
    // Unstructured concurrency for all the cooks

    let workTask = Task {
        try await withThrowingTaskGroup(of: Void.self) { group in
            for cook in staff.keys {
                group.addTask { try await cook.handleShift() }
            }

            try await group.waitForAll()
            print("all work done")
        }
    }

    // Unstructured concurrency for the end of the shift

    let cancelTask = Task {
        try await Task.sleep(for: shiftDuration) // keep the restaurant going until closing time
        print("ending shift")
        workTask.cancel()                        // at the end of the shift, tell the cooks to stop working
    }

    // And because we're using unstructured concurrency, we need to manually handle
    // cancelation of `run`, itself.

    try await withTaskCancellationHandler {
        _ = try await workTask.value             // perform all the work of `handleShift` for all the cooks
        cancelTask.cancel()                      // if all the work finished, then `cancelTask` is no longer needed and should be canceled, itself; obviously, if the timeout `Task` ended up getting invoked (i.e., work was still in progress at the end of the shift), then we will not reach this line because `workTask` will throw `CancellationError`
    } onCancel: {
        cancelTask.cancel()                      // if `run` was canceled, though (e.g., there was a fire and the restaurant needed to be closed prematurely; lol), then just clean up
        workTask.cancel()
    }
}

或者,您可以使用他们的模式保持 struct 化并发,但将所有厨师的工作包装在组中的组中:

func run() async throws {
    try await withThrowingTaskGroup(of: Void.self) { [staff, shiftDuration] group in
        group.addTask {
            try await withThrowingDiscardingTaskGroup { group in // or `withThrowingTaskGroup(of: Void.self)` in older versions
                for cook in staff.keys {
                    group.addTask { try await cook.handleShift() }
                }
            }
            print("all work done")
        }

        group.addTask {
            try await Task.sleep(for: shiftDuration) // keep the restaurant going until closing time
            print("ending shift")
        }

        try await group.next()
        group.cancelAll()
    }
}

这两个都有点笨拙(尤其是对于WWDC观众来说),所以我可以理解为什么苹果过度简化了他们的例子(但以牺牲他们据称试图解决的业务问题的正确性为代价).我怀疑他们只是想说明你可以取消任务组,并且不想陷入这样的困境.但这两个修改后的片段是常见"超时"模式的示例.这可能是他们试图说明的那种业务问题的更正确的实现.

Swift相关问答推荐

对多个项目的数组进行排序

如何将EnvironmentObject从UIKit视图控制器传递到SwiftUI视图

Swift C外部实现的Task / Future类类型

按SWIFT中每个ID的最大值进行筛选/排序

SwiftUI同心圆,通过逐渐出现来显示进度

为什么要在SWIFT RandomAccessCollection协议中重新定义元素类型?

异步/等待函数的XCTAssertThrowsError

在 SwiftUI 视图中观察 UIViewRepresentable 的 @State var 变化

ClosedRange.Swift 中 Int 的索引?

暂停我的 AR 会话并清除变量和锚点的功能

Swift:结果的失败类型不能是协议 - Type 'any ShadowError' cannot conform to Error

如何返回一个通过 SwiftUI 中的另一个协议间接继承 View 协议的 struct ?

Swift - 订阅视图之外的绑定值

如何在填充上添加 if else 语句? SwiftUI

用于 Swift 编码的 iOS 应用程序的 Ffmpeg

EXC_BREAKPOINT 崩溃的原因范围

如何实现 Swift 的 Comparable 协议?

Swift 3:日期与 NSDate?

如何在Swift中找出字母是字母数字还是数字

显示 UIAlertController 的简单 App Delegate 方法(在 Swift 中)