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
相关产品推荐
相关产品推荐

