【问题标题】:Structured Concurrency difference between addTask and addTaskUnlessCancelled SwiftaddTask 和 addTaskUnlessCancelled Swift 之间的结构化并发区别
【发布时间】:2022-01-16 15:49:35
【问题描述】:

我对调用 addTask()addTaskUnlessCancelled 感到困惑。根据定义 addTask() 在您的组中将无条件添加一个新任务到 group

func testCancellation() async {
    do {
        try await withThrowingTaskGroup(of: Void.self) { group -> Void in
            group.addTaskUnlessCancelled {
                print("added")
                try await Task.sleep(nanoseconds: 1_000_000_000)
                throw ExampleError.badURL
            }
            group.addTaskUnlessCancelled {
                print("added")
                try await Task.sleep(nanoseconds: 2_000_000_000)
                print("Task is cancelled: \(Task.isCancelled)")
            }

            group.addTaskUnlessCancelled {
                print("added")
                try await Task.sleep(nanoseconds: 5_000_000_000)
                print("Task is cancelled: \(Task.isCancelled)")
            }
            group.cancelAll()
            try await group.next()

        }
    } catch {
        print("Error thrown: \(error.localizedDescription)")
    }
}

如果您想避免将任务添加到已取消的组中,我们必须使用addTaskUnlessCancelled() 方法。但即使使用group.cancelAll(),它也会将所有任务添加到组中。那么这里和只有在Task 抛出一些错误时才返回true 的返回值有什么区别?

【问题讨论】:

    标签: ios swift asynchronous async-await concurrency


    【解决方案1】:

    简答

    addTaskUnlessCancelled 的优点在于:

    • 如果组被取消,它甚至不会启动任务;和
    • 如果组已被取消,它允许您添加提前退出。

    如果您有兴趣,可以将addTask 的源代码与addTaskUnlessCancelled 的源代码进行比较,就在其下方。


    长答案

    我认为,如果我们更改任务组是随着时间的推移处理请求(例如,使用AsyncSequence),则问题是最好的例证。

    考虑以下

    func a() async {
        await withTaskGroup(of: Void.self) { group in
            for await i in tickSequence() {
                group.addTask(operation: { await self.b() })
    
                if i == 3 {
                    group.cancelAll()
                }
            }
        }
    }
    
    func b() async {
        let start = CACurrentMediaTime()
        while CACurrentMediaTime() - start < 3 { }
    }
    
    func tickSequence() -> AsyncStream<Int> {
        AsyncStream { continuation in
            Task {
                for i in 0 ..< 12 {
                    try await Task.sleep(nanoseconds: NSEC_PER_SEC / 2)
    
                    continuation.yield(i)
                }
                continuation.finish()
            }
        }
    }
    

    在此示例中,b 正在等待刻度序列事件,为每个事件调用 c。但是我们在添加第四个任务后取消该组。结果如下:

    所以我们可以看到路标ⓢ,在添加第四个任务后,该组被取消了,但它没有受到影响。

    但那是因为b 没有响应取消。所以假设你解决了这个问题:

    func b() async {
        let start = CACurrentMediaTime()
        while CACurrentMediaTime() - start < 3 {
            if Task.isCancelled { return }
        }
    }
    

    您现在看到如下行为:

    这更好,因为b 现在正在取消,但a 仍在尝试向组添加任务,即使它已被取消。并且可以看到b在反复运行(虽然至少现在是立即终止),而a发起的任务并没有及时完成。

    但是,如果你使用group.addTaskUnlessCancelled,它不仅不会向组中添加新任务(即,它不依赖于任务的取消功能),而且还可以让你在组结束时退出取消。

    func a() async {
        await withTaskGroup(of: Void.self) { group in
            for await i in tickSequence() {
                guard group.addTaskUnlessCancelled(operation: { await self.b() })
                else { break }
    
                if i == 3 {
                    group.cancelAll()
                }
            }
        }
    }
    

    这会导致所需的行为:

    显然,这是一个相当人为的示例,明确构建以说明差异,但在许多情况下,addTaskaddTaskUnlessCancelled 之间的差异不那么明显。但希望以上说明了差异。

    最重要的是,如果您可能在向该组添加其他任务的过程中取消组,建议您使用addTaskUnlessCancelled。话虽如此,如果您不取消组(例如,取消任务比取消组更常见,恕我直言),并不完全清楚真正需要多久addTaskUnlessCancelled


    注意,在上面,我删除了所有 OSLog 和路标代码,以在 Xcode Instruments 中生成所有上述间隔和路标(因为我不想分散手头的问题)。但是,如果您有兴趣,这是实际的代码:

    import os.log
    
    private let log = OSLog(subsystem: "Test", category: .pointsOfInterest)
    
    func a() async {
        await withTaskGroup(of: Void.self) { group in
            let id = log.begin(name: #function, "begin")
            defer { log.end(name: #function, "end", id: id) }
    
            for await i in tickSequence() {
                guard group.addTaskUnlessCancelled(operation: { await self.b() }) else { break }
    
                if i == 3 {
                    log.event(name: #function, "Cancel")
                    group.cancelAll()
                }
            }
        }
    }
    
    func b() async {
        let id = log.begin(name: #function, "begin")
        defer { log.end(name: #function, "end", id: id) }
    
        let start = CACurrentMediaTime()
        while CACurrentMediaTime() - start < 3 {
            if Task.isCancelled { return }
        }
    }
    
    func tickSequence() -> AsyncStream<Int> {
        AsyncStream { continuation in
            Task {
                for i in 0 ..< 12 {
                    try await Task.sleep(nanoseconds: NSEC_PER_SEC / 2)
    
                    continuation.yield(i)
                }
                continuation.finish()
            }
        }
    }
    

    extension OSLog {
        func event(name: StaticString = "Points", _ string: String) {
            os_signpost(.event, log: self, name: name, "%{public}@", string)
        }
    
        /// Manually begin an interval
        func begin(name: StaticString = "Intervals", _ string: String) -> OSSignpostID {
            let id = OSSignpostID(log: self)
            os_signpost(.begin, log: self, name: name, signpostID: id, "%{public}@", string)
            return id
        }
    
        /// Manually end an interval
        func end(name: StaticString = "Intervals", _ string: String, id: OSSignpostID) {
            os_signpost(.end, log: self, name: name, signpostID: id, "%{public}@", string)
        }
    
        func interval<T>(name: StaticString = "Intervals", _ string: String, block: () throws -> T) rethrows -> T {
            let id = OSSignpostID(log: self)
    
            os_signpost(.begin, log: self, name: name, signpostID: id, "%{public}@", string)
            defer { os_signpost(.end, log: self, name: name, signpostID: id, "%{public}@", string) }
            return try block()
        }
    }
    

    【讨论】:

      猜你喜欢
      • 2011-04-16
      • 1970-01-01
      • 2021-04-12
      • 2011-12-07
      • 1970-01-01
      • 2021-10-17
      • 2015-02-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多