AVURLAsset初始化致主线程冻结问题求助
解决方案:AVQueuePlayer批量创建AVURLAsset时偶现卡顿问题
问题核心分析
AVURLAsset的初始化并非完全无开销,底层会同步执行URL校验、资源元数据预解析(甚至可能触发轻量网络请求)。批量创建时,若遇到网络波动、DNS解析延迟或系统资源紧张,单个asset的初始化可能阻塞线程,进而导致整个队列的创建流程卡住——哪怕放到后台线程,也会因为并发任务过多或单个任务无限阻塞,引发应用整体冻结。
有效修复方案
1. 限制并发创建数量,避免资源过载
一次性创建10个AVURLAsset会瞬间触发大量底层操作,用信号量限制并发数,让系统有足够资源处理每个任务:
// 定义全局并发队列和信号量(限制最多3个并发) private let assetCreationQueue = DispatchQueue(label: "com.yourapp.asset.creation", attributes: .concurrent) private let creationSemaphore = DispatchSemaphore(value: 3) // 独立的resourceLoader串行队列(避免与其他操作冲突) private lazy var resourceLoaderQueue: DispatchQueue = { DispatchQueue(label: "com.yourapp.resource.loader", attributes: .serial) }() func loadAssets() { let urlStrings = [ "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4", "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4", "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4", "http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerFun.mp4" ] for urlString in urlStrings { guard let contentURL = URL(string: urlString) else { // 处理无效URL continue } assetCreationQueue.async { [weak self] in guard let self = self else { return } self.creationSemaphore.wait() defer { self.creationSemaphore.signal() } self.createAVAssetWithTimeout(url: contentURL) { [weak self] asset in guard let self = self, let asset = asset else { return } let playerItem = self.createPlayerItemForAsset(asset) DispatchQueue.main.async { self.queuePlayer?.insert(playerItem, after: nil) } } } } }
2. 给AVURLAsset初始化添加超时保护
针对可能卡住的初始化操作,用DispatchWorkItem设置超时,避免线程永久阻塞:
private func createAVAssetWithTimeout(url: URL, completion: @escaping (AVURLAsset?) -> Void) { let workItem = DispatchWorkItem { // 优化初始化选项:关闭精确时长计算,减少同步开销 let assetOptions: [String: Any] = [ AVURLAssetPreferPreciseDurationAndTimingKey: false ] let asset = AVURLAsset(url: url, options: assetOptions) asset.resourceLoader.setDelegate(self, queue: self.resourceLoaderQueue) completion(asset) } // 执行任务 assetCreationQueue.async(execute: workItem) // 10秒超时后取消任务 DispatchQueue.global().asyncAfter(deadline: .now() + 10) { if !workItem.isCancelled { workItem.cancel() completion(nil) // 这里可以添加超时后的处理逻辑,比如提示用户或添加占位播放项 } } }
3. 优化AVURLAsset初始化选项
设置AVURLAssetPreferPreciseDurationAndTimingKey为false,让AVURLAsset不在初始化阶段就精确计算视频时长,大幅减少同步操作的耗时。
4. 确保ResourceLoader队列独立
resourceLoader的回调会在指定队列执行,必须用独立的串行队列,避免和主线程、其他共享队列的操作互相阻塞。
为什么之前的方案无效?
- 后台线程方案:仅把任务移到后台,但未限制并发数,批量任务仍会瞬间占用大量系统资源,单个卡住的任务会阻塞整个后台队列的后续执行。
- loadValuesAsynchronously方案:问题出在AVURLAsset初始化阶段,还没走到异步加载步骤就卡住了,所以该方案无法覆盖初始化阻塞的场景。
内容的提问来源于stack exchange,提问作者Mradul Kumar
相关产品推荐
相关产品推荐

