深入理解 Swift TaskGroup:从基础用法到性能优化的完整指南

大家好,我是 展菲,目前在上市企业从事人工智能项目研发管理工作,平时热衷于分享各种编程领域的软硬技能知识以及前沿技术,包括iOS、前端、Harmony OS、Java、Python等方向。在移动端开发、鸿蒙开发、物联网、嵌入式、云原生、开源等领域有深厚造诣。
图书作者:《ESP32-C3 物联网工程开发实战》
图书作者:《SwiftUI 入门,进阶与实战》
超级个体:COC上海社区主理人
特约讲师:大学讲师,谷歌亚马逊分享嘉宾
科技博主:华为HDE/HDG
我的博客内容涵盖广泛,主要分享技术教程、Bug解决方案、开发工具使用、前沿科技资讯、产品评测与使用体验。我特别关注云服务产品评测、AI 产品对比、开发板性能测试以及技术报告,同时也会提供产品优缺点分析、横向对比,并分享技术沙龙与行业大会的参会体验。我的目标是为读者提供有深度、有实用价值的技术洞察与分析。
展菲:您的前沿技术领航员
👋 大家好,我是展菲!
📱 全网搜索“展菲”,即可纵览我在各大平台的知识足迹。
📣 公众号“Swift社区”,每周定时推送干货满满的技术长文,从新兴框架的剖析到运维实战的复盘,助您技术进阶之路畅通无阻。
💬 微信端添加好友“fzhanfei”,与我直接交流,不管是项目瓶颈的求助,还是行业趋势的探讨,随时畅所欲言。
📅 最新动态:2025 年 3 月 17 日
快来加入技术社区,一起挖掘技术的无限潜能,携手迈向数字化新征程!
文章目录
- 前言
- TaskGroup 是什么?它解决了什么问题?
- TaskGroup 的基础用法(可运行 Demo)
- 解析:
- 实际场景举例:
- 如何在 TaskGroup 中处理取消?
- 场景举例:
- 为什么一次性创建 1000 个子任务会导致内存暴涨?
- 正确方式:限制并发数量,边消费边添加
- 代码解析:为什么这样做更好?
- 1. 一次只保持 10 个任务在内存中
- 2. “边消费边补充”的思路就像:
- 3. addTaskUnlessCancelled
- 实际项目中的典型使用场景
- 批量网络请求
- 批量文件读取
- 图像处理/视频处理
- 分布式任务(结合 Swift server)
- 总结
前言
Swift 的结构化并发(Structured Concurrency)里,TaskGroup 是一个非常强大的能力,它让我们可以更自然、更安全地并行处理一批任务,比如批量请求接口、并行读取文件、并行处理图片等。
这一篇我们就从一个可运行的例子开始,逐步讲清楚:
- TaskGroup 的基本使用方法
- 如何取消 TaskGroup
- 为什么大量任务会导致内存占用暴涨
- 如何批量并发但避免同时创建成千上万个任务
- 如何在真实项目里落地
并且所有示例都采用 Swift Concurrency(要求 iOS 15+),如果是早期版本也可以通过 back-deploy 运行。
TaskGroup 是什么?它解决了什么问题?
在 Swift 并发之前,想要并发执行多个任务我们通常会使用:
- DispatchGroup
- OperationQueue
- 手写 semaphore 配合线程池
但是这些方案都有明显缺陷,比如:
- 逃逸闭包容易写出垃圾代码
- 线程过多导致性能下降
- 取消逻辑很难处理
- 错误传递不够优雅
TaskGroup 则是结构化并发中的重要基础设施,它允许我们:
- 动态创建任意数量的子任务
- 等待所有任务完成
- 优雅地处理取消
- 任务之间隔离、无共享状态
- 不会创建无限线程,因为使用的是协作式线程池(Cooperative Thread Pool)
一句话总结:
TaskGroup = 更现代化、更安全的并行任务管理方式。
TaskGroup 的基础用法(可运行 Demo)
我们先来看一个简单的例子:并行抓取一批 URL 内容。
下面是一个可运行的 Swift 示例(你可以丢进 playground 运行):
import Foundation@MainActor
@Observable
final class Store {private(set) var messages: [String] = []func fetch(urls: [URL]) async {messages = await withTaskGroup(of: String.self,returning: [String].self) { group in// 添加子任务for url in urls {group.addTask(priority: .high) {return await self.fetch(url)}}var result: [String] = []// 等待所有任务完成for await message in group where !message.isEmpty {result.append(message)}return result}}private func fetch(_ url: URL) async -> String {// 模拟网络请求try? await Task.sleep(for: .seconds(1))return "内容来自:\(url.absoluteString)"}
}
解析:
withTaskGroup用来创建任务组addTask添加子任务for await遍历所有返回结果- 子任务的执行是并行的
实际场景举例:
你在开发一个新闻类 App,需要同时获取多个频道的最新文章:
- 推荐频道
- 热门频道
- 本地频道
- 视频频道
这里当然可以写多条异步调用,但你更希望:
- 所有请求是并行的(更快)
- 任何请求失败不要影响整体
- 能够按顺序收集结果
TaskGroup 就非常适合做这种批处理操作。
如何在 TaskGroup 中处理取消?
TaskGroup 使用的是协作式取消(Cooperative Cancellation),也就是说任务本身必须主动检查是否被取消。
例如:
group.addTask {if Task.isCancelled {return ""} else {return await fetch(url)}
}
你也可以在遍历结果时主动取消:
for await message in group {messages.append(message)// 比如只要拿到 3 条数据就不继续等了if messages.count > 3 {group.cancelAll()}
}
场景举例:
你正在做批量 OCR 识别图片。
前端用户只需要展示前 3 张结果,其余的就不需要继续识别了。
TaskGroup 可以很轻松做到:
- 开始时并行识别 N 张
- 得到前 3 张后马上取消剩余任务
- 避免浪费 CPU
为什么一次性创建 1000 个子任务会导致内存暴涨?
Swift 并发使用协作式线程池,这意味着:
即使并发任务很多,CPU 线程数量是有限的。
但这并不代表你可以毫无顾忌地创建几千甚至几万条任务。
来看下面这个示例:
for url in urls { // urls 有 1000 条group.addTask {return await self.fetch(url)}
}
问题:
- 这 1000 个 task 虽然不会全部同时运行
- 但 它们都需要内存空间(约几十 KB 到几百 KB 不等)
- 在某些场景下甚至会导致 OOM(特别是 iOS)
所以我们需要引入“最大并发数量”,避免一次性创建太多任务。
正确方式:限制并发数量,边消费边添加
下面这个优化版本是最推荐的写法:
import Foundation@MainActor
@Observable
final class Store {private(set) var messages: [String] = []func fetch(urls: [URL]) async {messages = await withTaskGroup(of: String.self,returning: [String].self) { group inlet maxConcurrent = min(urls.count, 10)var index = -1// 先添加有限数量的任务for _ in 0..<maxConcurrent {group.addTask {index += 1return await self.fetch(urls[index])}}var result: [String] = []// 每完成一个任务,再补一个新的任务进来for await message in group where !message.isEmpty {result.append(message)if index < urls.count - 1 {group.addTaskUnlessCancelled {index += 1return await self.fetch(urls[index])}}}return result}}private func fetch(_ url: URL) async -> String {try? await Task.sleep(for: .milliseconds(300))return "内容来自:\(url.absoluteString)"}
}
代码解析:为什么这样做更好?
1. 一次只保持 10 个任务在内存中
避免出现:
- 1000 个 task 同时排队
- 每个 task 占几十 KB
- 最终占用几十 MB 内存
2. “边消费边补充”的思路就像:
像是请求工厂:最多 10 个工人同时工作,做完一个补一个。
3. addTaskUnlessCancelled
这个 API 会在 group 已经取消时拒绝添加新任务。
适用于:
- 大文件下载
- 批量数据处理
- 任务优先级调整
实际项目中的典型使用场景
下面给你几个真实业务场景,TaskGroup 都非常适用:
批量网络请求
例如财务系统要同时拉取:
- 订单列表
- 用户详情
- 账单列表
- 审批记录
TaskGroup 可以更快得到全部数据。
批量文件读取
比如从沙盒加载大量 JSON 文件:
- 配置文件
- 日志文件
- 离线数据包
使用 TaskGroup 可以并行读取,速度会快很多。
图像处理/视频处理
常见于:
- 批量生成缩略图
- OCR 识别
- AI 图片分块处理(比如 webp/png 编码)
分布式任务(结合 Swift server)
例如你正在写一个 Vapor / Hummingbird 服务,通过 TaskGroup 做:
- 并行访问多个微服务
- 汇总返回结果
- 自动取消无用请求
总结
TaskGroup 是 Swift 并发中非常值得掌握的能力,它帮助我们:
- 更优雅地处理并行任务
- 更安全地执行大量异步任务
- 自动处理取消
- 避免创建过多线程
- 在大任务场景中极大节省内存
并且它已经 back-deploy 到 iOS 13 / macOS 10.15,所以你几乎可以在所有项目中放心使用。
