如何创建用于后台线程任务的串行Dispatch Group?
实现后台串行视频分段导出的正确姿势
嘿,我来帮你搞定这个视频分段导出的串行任务问题!其实核心思路就是用一个后台串行队列控制任务顺序,配合DispatchGroup跟踪每个导出任务的生命周期,确保前一个导出完成后再启动下一个。下面给你完整的实现方案和细节说明:
1. 核心组件梳理
首先我们需要几个关键部分:
- 一个后台串行队列:保证同一时间只有一个导出任务在运行,避免CPU/磁盘资源过载(视频导出属于高负载任务)
- DispatchGroup:用来精准跟踪每个导出任务的开始与结束,防止任务遗漏导致的流程中断
- 递归处理逻辑:逐个启动分段导出,当前任务完成后自动触发下一个,完美贴合你要的串行执行需求
2. 完整代码实现
先上可直接复用的代码,后面再拆解细节:
import AVFoundation // 定义视频分段模型,统一管理每个分段的参数 struct VideoSegment { let asset: AVAsset let timeRange: CMTimeRange let outputURL: URL } class VideoExporter { func exportSegmentsSequentially(segments: [VideoSegment]) { // 创建后台串行队列,设置background服务质量适配后台任务 let exportQueue = DispatchQueue(label: "com.yourapp.video.export", qos: .background) let exportGroup = DispatchGroup() // 递归函数:处理第index个分段,完成后自动启动下一个 func processSegment(at index: Int) { guard index < segments.count else { // 所有分段处理完成,切回主队列更新UI DispatchQueue.main.async { print("✅ 所有视频分段导出完成!") // 这里可以添加UI逻辑,比如弹出成功提示、更新进度条等 } return } let currentSegment = segments[index] exportGroup.enter() // 标记启动一个新任务 exportQueue.async { // 创建AVAssetExportSession实例 guard let exportSession = AVAssetExportSession( asset: currentSegment.asset, presetName: AVAssetExportPresetHighestQuality ) else { print("❌ 无法创建导出会话(分段\(index+1))") exportGroup.leave() // 任务失败也要标记结束 processSegment(at: index + 1) // 继续下一个分段 return } // 配置导出参数 exportSession.outputURL = currentSegment.outputURL exportSession.outputFileType = .mov // 可根据需求调整为.mp4等格式 exportSession.timeRange = currentSegment.timeRange // 启动异步导出 exportSession.exportAsynchronously { defer { exportGroup.leave() } // 无论成功失败,确保标记任务结束 // 处理导出结果 switch exportSession.status { case .completed: print("✅ 分段\(index+1)导出成功:\(currentSegment.outputURL.path)") // 这里可以添加成功后的逻辑,比如保存路径到本地存储 case .failed: let errorMsg = exportSession.error?.localizedDescription ?? "未知错误" print("❌ 分段\(index+1)导出失败:\(errorMsg)") case .cancelled: print("⚠️ 分段\(index+1)导出被取消") default: break } // 启动下一个分段的导出 processSegment(at: index + 1) } } } // 启动第一个分段的导出流程 processSegment(at: 0) } } // 用法示例: // let sourceAsset = AVAsset(url: yourSourceVideoURL) // let segments = [ // VideoSegment(asset: sourceAsset, timeRange: CMTimeRange(start: .zero, duration: CMTime(seconds: 10, preferredTimescale: 600)), outputURL: yourOutputURL1), // // 填充剩下4个分段的配置... // ] // let exporter = VideoExporter() // exporter.exportSegmentsSequentially(segments: segments)
3. 关键细节解释
- 串行队列的意义:
DispatchQueue(label:..., qos: .background)是串行队列,添加到队列的任务会严格按顺序执行,不会同时运行多个导出任务,避免系统资源竞争导致的导出失败或卡顿。 - DispatchGroup的正确用法:每个导出任务启动前调用
enter(),结束后(无论成功失败)调用leave(),用defer确保leave()一定会被执行,防止Group一直处于等待状态。 - 递归逻辑的优势:相比循环等待,递归的方式更优雅,每个任务完成后自动触发下一个,完全符合你要的“等待上一个完成再启动下一个”的需求。
- 线程切换注意:AVAssetExportSession的回调是在后台队列的,所以如果要更新UI,必须切换到主队列(
DispatchQueue.main.async)。
4. 额外注意事项
- 输出路径校验:每个分段的
outputURL必须唯一,且所在目录必须存在。可以提前创建目录:let outputDir = currentSegment.outputURL.deletingLastPathComponent() try? FileManager.default.createDirectory(at: outputDir, withIntermediateDirectories: true) - 导出预设选择:
AVAssetExportPresetHighestQuality适合高质量导出,但速度较慢;如果追求导出速度,可以选择AVAssetExportPresetMediumQuality等预设,根据需求调整。 - 取消功能扩展:如果需要支持取消导出,可以给
VideoExporter添加isCancelled属性,在导出前检查状态,调用exportSession.cancel()终止任务。
内容的提问来源于stack exchange,提问作者Roi Mulia
相关产品推荐
相关产品推荐

