Swift中按需等待异步结果的正确实现(解决信号量死锁)
问题分析与解决方案
兄弟,你这代码踩了信号量在异步场景下的经典坑——死锁!我来给你拆解一下问题,再给你几个靠谱的实现方式:
为什么会无限等待?
假设你在主队列调用getTaskId,而getGroupId的回调也是在主队列执行的话:
- 你调用
semaphore.wait()会直接阻塞当前的主队列线程 - 这时候
getGroupId的回调代码根本没机会在主队列上执行,自然semaphore.signal()永远不会被调用 - 结果就是信号量一直等下去,陷入死锁
就算不是主队列,如果getGroupId的回调和wait()在同一个串行队列,也会出现同样的问题。信号量这种方式只适合在不同队列之间同步,不能在同一个串行队列里这么玩。
正确的实现方式
方式1:用回调嵌套(兼容所有Swift版本)
把后续依赖groupId的逻辑抽成一个单独的函数,不管是异步拿到groupId还是直接传入的,都统一调用这个函数继续执行,完全用异步回调的方式处理,避免阻塞线程:
func getTaskId(uid: String, groupName: String?, groupId: String?, taskName: String, handler: @escaping ([String]?, Error?) -> Void) { // 把需要用groupId执行的逻辑抽出来 func proceedWithValidGroupId(_ finalGroupId: String) { // 这里放你原来在semaphore.wait()之后的代码,比如获取taskId的逻辑 // 举个例子:假设你有个异步获取taskId的方法 fetchTaskIds(forGroupId: finalGroupId, taskName: taskName) { taskIds, fetchErr in if let err = fetchErr { handler(nil, err) return } handler(taskIds, nil) } } // 处理groupId的获取逻辑 if let groupName = groupName { getGroupId(uid: uid, groupName: groupName) { fetchedGroupId, groupErr in if let err = groupErr { handler(nil, err) return } guard let validGroupId = fetchedGroupId else { let err = xErrors.getTaskIdErr(message: "Failed to get group id") handler(nil, err) return } proceedWithValidGroupId(validGroupId) } } else if let providedGroupId = groupId { proceedWithValidGroupId(providedGroupId) } else { let err = xErrors.getTaskIdErr(message: "Failed to get group id") handler(nil, err) } }
方式2:用Async/Await(Swift 5.5+ 首选)
如果你的项目已经用上了Swift 5.5及以上版本,async/await是最简洁的方案,完全摆脱回调地狱和死锁风险:
首先把回调式的getGroupId包装成async函数:
// 假设getGroupId是某个类的方法,这里给它加个async扩展 extension YourGroupService { // 替换成getGroupId所属的实际类名 func getGroupId(uid: String, groupName: String) async throws -> String { return try await withCheckedThrowingContinuation { continuation in self.getGroupId(uid: uid, groupName: groupName) { groupId, err in if let err = err { continuation.resume(throwing: err) } else if let groupId = groupId { continuation.resume(returning: groupId) } else { continuation.resume(throwing: xErrors.getTaskIdErr(message: "Failed to get group id")) } } } } }
然后把getTaskId重写成async函数:
func getTaskId(uid: String, groupName: String?, groupId: String?, taskName: String) async throws -> [String] { let finalGroupId: String // 确定最终的groupId if let groupName = groupName { finalGroupId = try await getGroupId(uid: uid, groupName: groupName) } else if let providedId = groupId { finalGroupId = providedId } else { throw xErrors.getTaskIdErr(message: "Failed to get group id") } // 同样把获取taskId的回调方法也包装成async的,然后调用 return try await fetchTaskIds(forGroupId: finalGroupId, taskName: taskName) }
这样代码逻辑清晰,可读性拉满,还完全避免了同步阻塞带来的问题。
内容的提问来源于stack exchange,提问作者andy
相关产品推荐
相关产品推荐

