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

Dispatch Semaphore内修改外部变量无效及async-await改造咨询

Dispatch Semaphore异步修改对象属性不生效问题解析与async-await优化方案

原问题代码的核心错误

你遇到的问题本质是信号量signal()的调用时机错误:
saveImagesToFireBaseStorage是带闭包回调的异步操作,你在调用该函数后立刻执行了semaphore.signal(),导致主线程的semaphore.wait()在图片上传完成、闭包修改page属性之前就解除了等待。也就是说,当你执行到打印page.picURL1或后续保存步骤时,闭包里的page.picURL1 = url代码还未执行,自然拿到的是旧值。

举个执行顺序的例子:

  1. 进入newQueue.async代码块
  2. 调用saveImagesToFireBaseStorage(发起异步上传,函数立刻返回)
  3. 执行semaphore.signal(),主线程wait()结束并继续向下执行
  4. 一段时间后图片上传完成,闭包才执行并修改page属性

此时主线程早已走到打印甚至保存逻辑,所以看到的都是未修改的旧值。

原代码的修复方案(基于Semaphore)

必须把semaphore.signal()移到异步闭包内部,确保只有当图片上传完成、属性修改完毕后才触发信号量;同时要处理无图片URL的情况,避免主线程永久等待:

func savePage(model: PageModel, savingHandler: @escaping (Bool) -> Void) {
    // some code .....
    
    var page = model
    let newQueue = DispatchQueue(label: "image upload queue")
    let semaphore = DispatchSemaphore(value: 0)

    newQueue.async {
        if let picURL1 = model.picURL1 {
            self.saveImagesToFireBaseStorage(pictureURL: picURL1) { urlString in
                if let url = urlString {
                    page.picURL1 = url
                }
                semaphore.signal() // 移到闭包内部,确保属性修改完成后发信号
            }
        } else {
            semaphore.signal() // 无图片时也要发信号,避免主线程阻塞
        }
    }
    semaphore.wait()

    newQueue.async {
        if let picURL2 = model.picURL2 {
            self.saveImagesToFireBaseStorage(pictureURL: picURL2) { urlString in
                if let url = urlString {
                    page.picURL2 = url
                }
                semaphore.signal()
            }
        } else {
            semaphore.signal()
        }
    }
    semaphore.wait()

    print(page.picURL1 ?? "nil") // 现在能拿到正确值
    
    newQueue.async {
        print(page.picURL1 ?? "nil")
        do {
            try pageDocumentRef.setData(from: page)
            savingHandler(true)
        } catch let error {
            print("Error writing city to Firestore: \(error)")
            savingHandler(false) // 错误场景也要回调外部
        }
        semaphore.signal()
    } 
    semaphore.wait()
}

你的async-await方案点评与优化

改用async-await的思路非常正确,相比手动管理Semaphore,这种写法更简洁易读,也避免了信号量时机错误的坑。这里给几个优化细节:

1. 并行执行图片上传提高效率

原updatePageWithNewImageURLS里的三个await是串行执行的,若图片上传之间无依赖,可改用async let实现并行上传:

private func updatePageWithNewImageURLS(_ page : PageModel) async throws -> PageModel {
    // 同时发起三个上传请求
    async let picUrl1 = saveImagesToFireBaseStorage(pictureURL: page.picURL1)
    async let picUrl2 = saveImagesToFireBaseStorage(pictureURL: page.picURL2)
    async let picUrl3 = saveImagesToFireBaseStorage(pictureURL: page.picURL3)
    
    // 等待所有请求完成
    let url1 = await picUrl1
    let url2 = await picUrl2
    let url3 = await picUrl3
    
    var newPage = page
    if let url1 = url1 { newPage.picURL1 = url1 }
    if let url2 = url2 { newPage.picURL2 = url2 }
    if let url3 = url3 { newPage.picURL3 = url3 }
    return newPage
}

2. 简化无异步逻辑的函数

addNewUrlstoPage仅做同步属性修改,无需标记async throws,可简化为:

private func addNewUrlstoPage(_ page : PageModel, url1: String?, url2 : String?, url3 :String?) -> PageModel {
    var newPage = page
    if let url1 = url1 { newPage.picURL1 = url1 }
    if let url2 = url2 { newPage.picURL2 = url2 }
    if let url3 = url3 { newPage.picURL3 = url3 }
    return newPage
}

3. 完善回调逻辑

在savePage的Task中,无论成功或失败都要调用savingHandler,避免外部永久等待:

func savePage(model: PageModel, savingHandler: @escaping (Bool) -> Void) {
    // Some code
    
    Task {
        do {
            let page = model
            let updatedPage = try await updatePageWithNewImageURLS(page)
            try pageDocumentRef.setData(from: updatedPage)
            savingHandler(true) // 成功回调
        } catch {
            print("Error: \(error)")
            savingHandler(false) // 失败回调
        }
    }
    
    // Some code
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 03:10:16