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

如何等待DispatchWorkItem执行完成且保证变量仅主线程可访问?

解决DispatchWorkItem等待与主线程变量访问的问题

嘿,你现在用轮询的方式确实有点“笨办法”的味道,不仅浪费资源,还容易因为线程安全问题出bug。结合你要求所有变量仅能被主线程访问的核心需求,我给你两个优雅的实现方案,既解决等待问题,又严格保证线程安全:

方案一:用DispatchGroup跟踪任务(推荐)

DispatchGroup是Apple官方推荐的跟踪异步任务完成的工具,内核级别的等待比轮询高效太多,而且我们可以轻松把变量的读写限制在主线程:

第一步:初始化变量与任务组

把需要保护的信息变量和DispatchGroup都放在ViewController里,任务启动时标记“进入组”,任务完成回到主线程更新变量后标记“离开组”:

class YourViewController: UIViewController {
    // 仅主线程读写的信息变量
    private var fetchedInfo: String?
    // 跟踪异步任务的DispatchGroup
    private let taskGroup = DispatchGroup()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 标记任务开始
        taskGroup.enter()
        DispatchQueue.global().async { [weak self] in
            // 模拟耗时的信息获取操作(比如网络请求、本地数据解析)
            Thread.sleep(forTimeInterval: 3)
            let newInfo = "从全局队列拿到的信息"
            
            // 必须回到主线程更新变量,同时标记任务完成
            DispatchQueue.main.async {
                self?.fetchedInfo = newInfo
                self?.taskGroup.leave()
            }
        }
    }
}

第二步:按钮点击时等待任务完成

在按钮点击事件里,调用taskGroup.wait()等待任务结束,同时因为变量的读写都在主线程,完全符合你的要求:

@IBAction func showInfoButtonTapped(_ sender: UIButton) {
    // 设置10秒超时,避免主线程无限阻塞
    let waitResult = taskGroup.wait(timeout: .now() + 10)
    
    switch waitResult {
    case .success:
        // 任务完成,直接在主线程访问变量
        if let info = fetchedInfo {
            print("展示信息:\(info)")
            // 这里可以做UI操作,比如更新label、弹出alert
        }
    case .timedOut:
        print("等待超时,信息还没获取完")
    }
}

方案二:用DispatchSemaphore实现等待

如果你对信号量更熟悉,也可以用DispatchSemaphore实现同样的逻辑,同样保证变量仅主线程访问:

class YourViewController: UIViewController {
    private var fetchedInfo: String?
    // 初始化信号量,初始值为0(表示没有可用资源)
    private let semaphore = DispatchSemaphore(value: 0)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        DispatchQueue.global().async { [weak self] in
            // 模拟耗时操作
            Thread.sleep(forTimeInterval: 3)
            let newInfo = "拿到的信息"
            
            // 主线程更新变量,然后发送信号
            DispatchQueue.main.async {
                self?.fetchedInfo = newInfo
                self?.semaphore.signal() // 信号量+1,通知等待的线程
            }
        }
    }
    
    @IBAction func showInfoButtonTapped(_ sender: UIButton) {
        // 等待信号,超时10秒
        let waitResult = semaphore.wait(timeout: .now() + 10)
        if waitResult == .success, let info = fetchedInfo {
            print("展示信息:\(info)")
        } else {
            print("等太久了,放弃")
        }
    }
}

关键注意点

  • 别让主线程堵太久:如果你的异步任务耗时超过1秒,主线程阻塞会导致UI卡顿,这种情况下建议改成“任务完成后自动启用按钮”的方式,用户点击时直接展示已准备好的信息,体验更好。
  • 弱引用避免内存泄漏:异步闭包里一定要用[weak self],不然ViewController会因为被闭包强引用而无法释放。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 08:09:29