在WWDC会话中探索Swift中的 struct 化并发.有一部分是关于任务组正常退出的.

虽然任务组是 struct 化并发的一种形式,但是组任务和任务组任务的任务树规则的实现方式有很小的差异.假设在迭代这个组的结果时,我遇到了一个子任务,它完成了一个错误.由于该错误被抛出组的块,组中的所有任务将被隐式取消,然后等待.它的工作原理就像一个tax—let.

当您的组通过正常的退出块而超出范围时,区别就出现了.因此,取消并不隐含.这种行为使您更容易使用任务组来表达fork—join模式,因为作业(job)只会等待而不会被取消.您也可以在退出块之前使用组的cancelAll方法手动取消所有任务.请记住,无论您如何取消一个任务,取消都会自动沿着树向下传播.

让我们举个例子.

func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] {
    var thumbnails: [String: UIImage] = [:]
    try await withThrowingTaskGroup(of: (String, UIImage).self) { group in
        for id in ids {
            group.async {
                return (id, try await fetchOneThumbnail(withID: id))
            }
        }
        // Obtain results from the child tasks, sequentially, 
        // in order of completion.
        for try await (id, thumbnail) in group {
            thumbnails[id] = thumbnail
        }
    }
    return thumbnails
}

这个例子中的正常出口是什么?当它达到return时,任务组完成了所有的任务,没有什么可以取消的.请帮助我理解这一点.

推荐答案

他们只是说,有了async let,如果你在任务超出范围之前忽略了await,它将被"隐式取消".但是对于任务组,如果你没有显式地await单独的组任务,这些任务将被隐式地取消.

当视频引用"正常退出"时,他们只是在谈论"快乐的路径",那里没有发生错误,那里没有什么被明确取消,执行只是正常完成,没有错误.他们在视频的其他地方谈到了明确的取消和错误处理;他们只是想让我们注意到async let在没有等待的情况下超出范围时的行为和相应的任务组行为之间的微妙差异.


考虑一下SE-0313个例子:

func go() async { 
    async let f = fast() // 300ms
    async let s = slow() // 3seconds
    print("nevermind...")
    // implicitly: cancels f
    // implicitly: cancels s
    // implicitly: await f
    // implicitly: await s
}

诚然,这是一个人为的例子,而不是你在实践中可能会做的事情.一个更现实的例子可能是一些代码,在创建任务async let之后,我们可能有一些逻辑使用了提前退出,导致它永远不会达到async let个任务中的一个或多个任务中的await.在这种情况下,使用async let,任何没有明确等待的任务都将被隐式取消.

概述了"隐式取消"的含义,现在让我们考虑一下你的例子:

func fetchThumbnails(for ids: [String]) async throws -> [String: UIImage] {
    try await withThrowingTaskGroup(of: (String, UIImage).self) { group in
        for id in ids {
            group.addTask { [self] in
                try await (id, fetchOneThumbnail(withID: id))
            }
        }
        
        // Obtain results from the child tasks, sequentially,
        // in order of completion.

        var thumbnails: [String: UIImage] = [:]

        for try await (id, thumbnail) in group {
            thumbnails[id] = thumbnail
        }

        return thumbnails
    }
}

(我把group.async换成了group.addTask,做了一些修饰,但这实际上和你的一样.

这实际上不适用于"隐式取消"的讨论,因为它有一个for循环,该循环为组中的每个任务都有一个await(当您在字典中累积结果时).因此,"隐式取消"的整个概念并不适用,因为所有的子任务都是显式等待的.

相反,让我们考虑主题的一个变体,可能是fetchOneThumbnail实际上没有返回任何东西,只是更新了一些内部缓存.那么它可能是:

func fetchThumbnails(for ids: [String]) async {
    await withTaskGroup(of: Void.self) { group in
        for id in ids {
            group.addTask { [self] in
                await fetchOneThumbnail(withID: id)
            }
        }

        // NB: no explicit `for await` loop of `group` sequence is needed; unlike 
        // `async let` pattern, these tasks will *not* be implicitly canceled
    }
}

但是在这个例子中,即使我们从来没有for await group序列(因此,没有任何子任务被显式等待,与前面的例子相反),这将not隐式地取消组中的任务,而不像async let.它会自动为我们完成所有这些任务.

简而言之,async let将隐式取消未显式等待的任何内容,而任务组not将隐式取消任何内容(至少在"正常退出"场景中),而是隐式await子任务.

Swift相关问答推荐

如何在Swift 中创建移动的uil标签?

JSON如何解码空对象

日期格式yyyyyy—MM—dd hh:mm:ss +0000到dd MMM,以swift格式表示""""

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

通过SwiftUI中的列表 Select 从字典中检索值

它是RxSwift中直接用于可扩展视图的有效的可扩展BehaviorRelay值?

是否可以循环遍历SWIFT子类并访问它们的静态变量S?

swift:只要框架名称中有一个带有框架名称的公共实体,就指定框架中公共 struct 的路径

如何增加 NSStatusBar 图像大小?

将matchedGeometryEffect 应用于列表视图内的视图时,列表视图中的项目会受到影响 - SwiftUI

快速并行读取进程 standardOutput 和 standardError 而不会阻塞

如何在闭包中使用构造 await sync

在 RealmSwift 中表示范围值?

如何在 swiftui 中设置给定字符串类型日期的日期倒计时?

从 actor 的 init 方法调用方法

如何让 SwiftUI 中的文本在旋转框架的同时侧向显示?

在 Swift 中,如何查看 Process() 传递给 shell 的字符串?

Swift :如何在一组字符之后得到所有东西

如何在 SwiftUI 中以编程方式滚动列表?

Swift 中的单元测试致命错误