语境

假设我想使用SWIFT并发在后台下载300张图片.我想要两样东西:

  1. 尽可能多的平行.
  2. 我的调用者在下载图像时接收每个图像,而不是等待所有图像都完成.

方法:

TaskGroupmany advantages:伟大的并行性、非常廉价的子任务和取消.但它不会返回,直到所有300个子任务都完成.

AsyncStream可以让我在下载时返回图片,但它本身没有并行性--下载是一次一个,按顺序进行的.

问题:

我想做的是用AsyncStream来包装一个TaskGroup,就像这样:

let stream = AsyncStream(NSImage.self) { continuation in
    
    _ = await withTaskGroup(of: NSImage.self, returning: [NSImage].self) { taskGroup in
        
        let imageURLs: [URL] = ... // array of 300 URLs to download
        for imageURL in imageURLs {
            taskGroup.addTask { await downloadImage(url: imageURL) }
        }

        for await result in taskGroup {
            continuation.yield(result)
        }

        continuation.finish()
        return []
    }
}

AsyncStream不能接受async的收盘.那么,使用SWIFT并发实现这一行为的最佳方式是什么?

推荐答案

其 idea 是,通过为异步工作创建Task,您可以从AsyncStream的同步上下文过渡到SWIFT并发.还请记住添加onTermination结束,以便它将响应AsyncStream的取消:

func images(for urls: [URL]) -> AsyncStream<NSImage> {
    AsyncStream { continuation in
        let task = Task {
            await withTaskGroup(of: NSImage.self) { group in
                for url in urls {
                    group.addTask { await self.downloadImage(url: url) }
                }

                for await image in group {
                    continuation.yield(image)
                }

                continuation.finish()
            }
        }

        continuation.onTermination = { _ in
            task.cancel()
        }
    }
}

显然,由于您同时执行所有这些网络请求,您必须认识到这些请求很可能不会按照与原始URL数组对应的顺序完成.

因此,您可以返回原始URL和结果图像的元组:

func images(for urls: [URL]) -> AsyncStream<(URL, NSImage)> {
    AsyncStream { continuation in
        let task = Task {
            await withTaskGroup(of: (URL, NSImage).self) { group in
                for url in urls {
                    group.addTask { await (url, self.downloadImage(url: url)) }
                }

                for await tuple in group {
                    continuation.yield(tuple)
                }

                continuation.finish()
            }
        }

        continuation.onTermination = { _ in
            task.cancel()
        }
    }
}

或者可能是索引号和图像:

func images(for urls: [URL]) -> AsyncStream<(Int, NSImage)> {
    AsyncStream { continuation in
        let task = Task {
            await withTaskGroup(of: (Int, NSImage).self) { group in
                for (index, url) in urls.enumerated() {
                    group.addTask { await (index, self.downloadImage(url: url)) }
                }

                for await tuple in group {
                    continuation.yield(tuple)
                }

                continuation.finish()
            }
        }

        continuation.onTermination = { _ in
            task.cancel()
        }
    }
}

Swift相关问答推荐

如何避免使用DispatchSemaphores时线程爆炸?

在visionOS RealityView中使用.GenerateText时,未显示Reality Composer Promaterial 纹理

在SWIFT中使用Objective-C struct 时出错(在作用域中找不到类型)

为SwiftUI文本视图提供固定宽度,并仅在文本换行时使文本视图底部增大

我可以在预览中通过拖动手势跳转,但在模拟器中失败

SWIFT计划计时器方法在时间间隔后未被调用

background Task(.backgroundTask)不适用于MySQL

如何在 SwiftUI 中为过渡动画保持相同的标识

为什么我的 tableView 没有推送到 tableView 内的 detailViewController?

如何在 AppDelegate for Cocoa macOS 中创建 mainMenu 和菜单项?

如何在 Combine 合并运算符的输出上使用 eraseToAnyPublisher

当使用 CGFloat 而不是数字文字时,为什么 node 的位置会发生变化?

状态变量更改后,SwiftUI 视图不会更改

为什么 SwiftUI 不在工具栏菜单中反映 @State 属性值?

`IndexSet` 永远不会是空的?

Swift 覆盖实例变量

Swift 中的 PageViewController 当前页面索引

如何使用 swift 将 Images.xcassets 中的图像加载到 UIImage 中

如何在 Swift 中遍历 struct 属性?

iOS:如何检测用户是否订阅了自动更新订阅