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

Swift Concurrency首次加载时Task内await后续代码不执行问题

问题现象
  • 绑定到UIBarButtonItem的并发方法首次触发异常,按钮初始化代码:.init(barButtonSystemItem: .camera, target: self, action: #selector(runClassification(_:))
  • 视图控制器首次加载完成后点击按钮,Task闭包内逻辑无法完整执行;将逻辑移出Task闭包可正常运行。执行过一次该函数、离开当前视图再返回点击,禁用导航栏按钮的逻辑可正常生效。
  • 调试确认:await调用前的打印、UI更新语句均可正常执行,await之后的业务代码无任何执行迹象,视图控制器初始化逻辑为常规写法无特殊定制。
关联代码

按钮触发的入口方法:

@objc func runClassification(_ sender: UIBarButtonItem) {
    Task {
        // 以下代码可以正常执行
        navigationItem.rightBarButtonItems?.forEach { $0.isEnabled = false }
        guard let image = imageView.image else { return }
        do {
            let response = try await MLClassifer.sharedManager.updateClassifications(capturedImage: image)
            // 首次调用时以下代码均不会执行
            print("MLClassifer.sharedManager.updateClassifications begining")
        } catch {
            debugPrint(error)
        }
    }
}

MLClassifier.swift核心实现:

private typealias ClassifierCheckedContinuation = CheckedContinuation<[VNRecognizedObjectObservation], Error>
private var classifierContinuation: ClassifierCheckedContinuation?

@MainActor
func updateClassifications(capturedImage: UIImage) async throws -> [VNRecognizedObjectObservation] {
    self.capturedImage = capturedImage
    let orientation = CGImagePropertyOrientation(capturedImage.imageOrientation)
    
    guard let ciImage = CIImage(image: capturedImage) else {
        throw MLClassiferError.invalidConversion
    }
    
    guard let model = self.model else {
        throw MLClassiferError.invalidModel
    }
    
    let mlModel = try VNCoreMLModel(for: model)
    
    let handler = VNImageRequestHandler(ciImage: ciImage, orientation: orientation)
    
    let request = VNCoreMLRequest(model: mlModel) { request, error in
        self.processClassifications(for: request, error: error)
    }
    
    try handler.perform([request])
    
    return try await withCheckedThrowingContinuation { (continuation: ClassifierCheckedContinuation) in
        self.classifierContinuation = continuation
    }
}

func processClassifications(for request: VNRequest, error: Error?) {
    if let error = error {
        classifierContinuation?.resume(throwing: error)
        return
    }

    guard let results = request.results, !results.isEmpty else {
        classifierContinuation?.resume(throwing: MLClassiferError.invalidConversion)
        return
    }
        
    guard let classifications = results as? [VNRecognizedObjectObservation], !classifications.isEmpty else {
        classifierContinuation?.resume(throwing: MLClassiferError.invalidConversion)
        return
    }
    
    guard let image = self.capturedImage,
          let capturedImage = CIImage(image: image)
    else {
        classifierContinuation?.resume(throwing: MLClassiferError.invalidConversion)
        return
    }

    classifierContinuation?.resume(returning: classifications)
    print("MLClassifer.sharedManager.updateClassifications returning")
}
问题根因

问题出在CheckedContinuation的赋值时序和Vision请求的执行逻辑冲突:

  • VNImageRequestHandler.perform()是同步阻塞方法,调用后会立刻在当前线程完成请求处理,传入的VNCoreMLRequest结果回调会在perform()返回前同步执行。
  • 现有代码先调用perform()触发请求、执行回调,再通过withCheckedThrowingContinuation给classifierContinuation属性赋值。首次点击时,回调执行到processClassifications的阶段,classifierContinuation还是nil,没有任何对象调用resume方法,await会永久挂起,后续代码自然不会执行。
  • 二次进入页面点击能“正常运行”只是时序巧合:首次调用流程走完后classifierContinuation已经被赋值,第二次调用时perform()触发的回调会resume第一次留存的continuation,造成逻辑正常执行的假象,本质是continuation和请求的对应关系完全错位。
修复方案

调整代码执行顺序,先创建并持有continuation,再触发Vision请求,保证回调执行时continuation已经完成赋值:

@MainActor
func updateClassifications(capturedImage: UIImage) async throws -> [VNRecognizedObjectObservation] {
    self.capturedImage = capturedImage
    let orientation = CGImagePropertyOrientation(capturedImage.imageOrientation)
    
    guard let ciImage = CIImage(image: capturedImage) else {
        throw MLClassiferError.invalidConversion
    }
    
    guard let model = self.model else {
        throw MLClassiferError.invalidModel
    }
    
    let mlModel = try VNCoreMLModel(for: model)
    
    return try await withCheckedThrowingContinuation { continuation in
        // 先持有continuation
        self.classifierContinuation = continuation
        let handler = VNImageRequestHandler(ciImage: ciImage, orientation: orientation)
        let request = VNCoreMLRequest(model: mlModel) { request, error in
            self.processClassifications(for: request, error: error)
        }
        do {
            // 再触发同步执行的Vision请求
            try handler.perform([request])
        } catch {
            // 捕获perform抛出的错误,避免continuation泄漏
            continuation.resume(throwing: error)
            self.classifierContinuation = nil
        }
    }
}

额外优化建议:

  • 每次执行完resume后立刻将classifierContinuation置为nil,避免多次resume同一个continuation触发运行时崩溃。
  • 不建议用单例属性持有continuation,多次快速点击会覆盖旧的continuation引用,导致旧任务永久挂起。更稳妥的写法是将continuation作为局部变量直接捕获在VNCoreMLRequest的回调闭包中,不需要单独定义属性存储。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 23:09:18