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

Swift中OperationQueue:限制同时2个异步请求,等待完成再启动下一个

解决Swift OperationQueue异步任务并发控制失效的问题

嘿,这个坑我之前踩过!问题根源在于普通的Operation默认是同步执行的——你的代码里,URLSession的下载任务一提交就立刻返回了,Operation会误以为自己已经执行完成,所以OperationQueue会不断往队列里塞新操作,完全无视你设置的maxConcurrentOperationCount = 2限制。

要解决这个问题,你需要自定义一个异步Operation,手动管理它的执行状态,让OperationQueue能准确识别任务是否真的完成。下面是具体的实现步骤:

1. 自定义异步下载Operation

我们需要重写Operation的几个关键属性和方法,来正确报告异步任务的执行状态:

class AsyncImageDownloadOperation: Operation {
    private let urlString: String
    private var task: URLSessionTask?
    // 可以添加闭包回调下载结果
    var completion: ((UIImage?) -> Void)?
    
    // 必须重写这两个属性,并支持KVO,让队列能感知状态变化
    private var _isExecuting = false
    override var isExecuting: Bool {
        get { return _isExecuting }
        set {
            willChangeValue(forKey: "isExecuting")
            _isExecuting = newValue
            didChangeValue(forKey: "isExecuting")
        }
    }
    
    private var _isFinished = false
    override var isFinished: Bool {
        get { return _isFinished }
        set {
            willChangeValue(forKey: "isFinished")
            _isFinished = newValue
            didChangeValue(forKey: "isFinished")
        }
    }
    
    // 标记这是异步操作
    override var isAsynchronous: Bool {
        return true
    }
    
    init(urlString: String) {
        self.urlString = urlString
        super.init()
    }
    
    override func start() {
        // 如果操作已取消,直接标记完成
        if isCancelled {
            isFinished = true
            return
        }
        
        isExecuting = true
        
        guard let url = URL(string: urlString) else {
            finish()
            return
        }
        
        let request = URLRequest(url: url)
        let session = URLSession(configuration: .default)
        task = session.dataTask(with: request) { [weak self] data, response, error in
            defer {
                self?.finish()
            }
            
            guard let self = self, !self.isCancelled else { return }
            
            // 处理下载结果,转成UIImage
            let image = data.flatMap(UIImage.init)
            // 回到主线程回调结果(如果需要更新UI)
            DispatchQueue.main.async {
                self.completion?(image)
            }
        }
        
        task?.resume()
    }
    
    override func cancel() {
        super.cancel()
        task?.cancel()
        finish()
    }
    
    private func finish() {
        isExecuting = false
        isFinished = true
    }
}

2. 使用自定义Operation控制并发

现在用这个自定义操作替代原来的逻辑,加入OperationQueue后,并发限制就会生效了:

let imageQueue = OperationQueue()
imageQueue.maxConcurrentOperationCount = 2 // 现在这个设置会正确生效

// 假设你有一堆需要加载的图片URL
let imageUrls = [
    "https://example.com/image1.jpg",
    "https://example.com/image2.jpg",
    "https://example.com/image3.jpg",
    "https://example.com/image4.jpg"
]

for url in imageUrls {
    let operation = AsyncImageDownloadOperation(urlString: url)
    operation.completion = { [weak self] image in
        // 这里拿到图片后,更新对应的tableView cell
        if let image = image {
            print("图片加载完成:\(url)")
            // self?.updateCellWithImage(image, forUrl: url)
        }
    }
    imageQueue.addOperation(operation)
}

为什么这样能解决问题?

  • 我们重写了isExecuting和isFinished属性并实现KVO通知,OperationQueue会通过这两个属性精准判断操作的真实状态。
  • 只有当下载任务真正完成(或被取消)时,我们才会把isFinished设为true,此时队列才会移除当前操作,再启动下一个操作。
  • 重写isAsynchronous明确告诉系统这是异步操作,避免队列误判任务完成时机。

额外小提示

  • 记得在cancel()方法里取消URLSessionTask,避免不必要的网络请求和资源浪费。
  • 如果需要复用URLSession,可以把session实例抽出来,不用每次创建新的session。

内容的提问来源于stack exchange,提问作者Trangler

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:48:07