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

使用DispatchGroup等待闭包完成致死锁,同步函数返回问题求助

问题描述

我有一个返回Bool类型的函数,该Bool值会在后台线程发起的网络请求完成后,在其闭包内被赋值。当前必须等待闭包执行完成才能获取返回值,但尝试用DispatchGroup实现等待逻辑时应用出现死锁阻塞;测试DispatchSemaphore也得到相同结果,且无法使用Async/Await。

补充:这段代码属于AppDelegate中处理推送通知时初始化的枚举,相关代码如下:

private func isOriginal() -> Bool {
    let group = DispatchGroup()
    var isOriginal = true
    group.enter()
    let parameters = GetIsOriginalParameters(completion: { result in
        defer {
            group.leave()
        }
        guard case .success(let isOriginalResult) = result else {
            return
        }
        isOriginal = isOriginalResult
    })
    getIsOriginalUseCase.run(parameters) // 发起网络请求
    group.wait()
    return isOriginal
}
public var navigationInfo: NavInfo {
    switch self {
    // ... 其他case
    case .createNew:
        return handleTypeNavigation()
    // ... 其他case
    }
}

private func handleTypeNavigation() -> NavInfo {
    if isOriginal() {
        return Navigation.original
    } else {
        return Navigation.notOriginal
    }
}
问题分析

死锁的核心原因是:group.wait()会阻塞当前线程,如果网络请求的完成闭包恰好需要在同一个线程(比如主线程)执行,就会形成死锁——被阻塞的线程无法处理闭包任务,闭包也就永远不会调用group.leave(),导致wait()无限等待下去。

另外,在属性的getter方法(比如navigationInfo)里做同步网络请求本身就是错误的设计,属性getter应该是轻量、无阻塞的操作,这种耗时的网络请求绝对不应该放在这里。

解决办法

1. 优先改成异步回调形式(推荐)

彻底放弃同步返回的思路,把判断逻辑改成异步闭包回调,从根源上避免阻塞:

// 重构isOriginal为异步函数
private func checkIsOriginal(completion: @escaping (Bool) -> Void) {
    let parameters = GetIsOriginalParameters(completion: { result in
        // 处理结果,默认值保持true
        let isOriginalResult: Bool
        if case .success(let value) = result {
            isOriginalResult = value
        } else {
            isOriginalResult = true
        }
        completion(isOriginalResult)
    })
    getIsOriginalUseCase.run(parameters)
}

// 同步修改handleTypeNavigation为异步
private func handleTypeNavigation(completion: @escaping (NavInfo) -> Void) {
    checkIsOriginal { isOriginal in
        let navInfo = isOriginal ? Navigation.original : Navigation.notOriginal
        completion(navInfo)
    }
}

之后需要调整navigationInfo的使用逻辑,不要直接通过属性getter获取,而是调用异步的handleTypeNavigation来拿到导航信息。

2. 迫不得已的同步方案(不推荐)

如果因为业务限制必须同步获取结果,要确保网络请求和闭包的执行线程不与等待线程冲突,比如强制让网络请求在全局队列执行,同时避免在主线程调用这个同步函数:

private func isOriginal() -> Bool {
    let semaphore = DispatchSemaphore(value: 0)
    var isOriginal = true
    let parameters = GetIsOriginalParameters(completion: { result in
        defer {
            semaphore.signal()
        }
        guard case .success(let isOriginalResult) = result else {
            return
        }
        isOriginal = isOriginalResult
    })
    
    // 强制让网络请求在全局后台队列执行,避免和等待线程冲突
    DispatchQueue.global(qos: .background).async {
        self.getIsOriginalUseCase.run(parameters)
    }
    
    // 注意:绝对不要在主线程调用这个方法,否则依然可能死锁或卡死界面
    semaphore.wait()
    return isOriginal
}

额外提醒

在推送通知处理的场景下,系统对处理时间有严格限制,如果做同步阻塞操作,很可能被系统判定为超时,甚至直接终止进程。如果是在didReceiveRemoteNotification方法中处理,一定要记得调用对应的completionHandler;如果是通知服务扩展,要注意在时间截止前完成操作。

内容的提问来源于stack exchange,提问作者Andoni Da Silva

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 10:55:15