You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 09:00:22