Swift调用API时代码无法退出Task的do块问题排查
问题修复方案
你之前猜测self.venues = getInitialVenueInfoWithAllOpenAtDateTimeProperties(…)赋值需要Task块包裹的方向是对的,但仅加Task包裹不足以解决问题,当前执行到第二个API请求的do块就挂起,是两个并发逻辑错误叠加导致的,和Yelp Fusion API本身无关:
- 主线程被同步阻塞:如果你在主线程(UIViewController的生命周期方法默认跑在主线程)直接调用异步方法,又用了
DispatchSemaphore.wait()、Task.result.get()这类同步等待方式强行转同步,会直接卡死主线程,后续第二个API请求的任务恢复逻辑根本得不到调度,自然卡在do块无法继续执行 - 多请求调度存在隐式阻塞:如果多日期请求的循环逻辑里用了自定义串行队列、非线程安全的共享请求实例,或者自己实现的异步转同步逻辑有死锁,也会导致后续请求永远无法拿到执行资源
修复步骤
1. 修正ViewController层的调用与UI赋值逻辑
绝对不要在主线程同步等待异步网络结果,正确做法是在视图加载时机用Task拉起异步上下文,所有UI属性赋值必须切到MainActor(主线程)执行:
// InitialBusinessResultsViewController.swift 对应调用位置 override func viewDidLoad() { super.viewDidLoad() // 拉起异步任务,不阻塞主线程 Task { // 网络请求在后台并发上下文执行,不卡住UI let fetchedVenues = await getInitialVenueInfoWithAllOpenAtDateTimeProperties( // 传入你需要的所有日期时间参数 ) // UI相关操作必须切到主线程执行 await MainActor.run { self.venues = fetchedVenues // 赋值完成后刷新视图 self.collectionView.reloadData() } } }
2. 重构多日期请求的聚合逻辑,消除手动阻塞
不要用信号量、锁之类的手段手动控制请求顺序,直接用系统提供的withTaskGroup实现并发请求、自动聚合结果,从根源避免死锁:
// GetInitialVenueInfo.swift 内方法实现参考 func getInitialVenueInfoWithAllOpenAtDateTimeProperties(dateTimeList: [TimeInterval]) async -> [Venue] { await withTaskGroup(of: [Venue].self) { group in var aggregatedVenues = [Venue]() // 为每个日期时间参数创建独立的并发请求任务 for timestamp in dateTimeList { group.addTask { do { // 这里放单个日期对应的Yelp API请求逻辑 let (data, _) = try await URLSession.shared.data( for: buildYelpRequest(openAtTimestamp: timestamp) ) let businessResults = try JSONDecoder().decode(YelpSearchResponse.self, from: data) return businessResults.businesses.map { Venue(businessModel: $0, openAt: timestamp) } } catch { print("时间戳\(timestamp)对应请求失败: \(error.localizedDescription)") return [] } } } // 按请求完成顺序聚合所有结果 for await singleTimeVenues in group { aggregatedVenues.append(contentsOf: singleTimeVenues) } // 按业务要求去重、整理后返回最终结果 return deduplicateVenues(aggregatedVenues) } }
3. 底层请求逻辑排查点
如果改完上述逻辑仍有挂起,直接检查FetchData.swift的实现:
- 不要用
URLSession.dataTask(with:completionHandler:)套信号量的方式做异步转同步,直接用系统原生的URLSession.data(for:)async接口,没有额外调度开销也不会死锁 - 挂起时看Xcode左侧导航的线程栈,如果主线程栈顶出现
semaphore_wait字样,说明还有残留的同步阻塞主线程的代码,删掉所有同步等待逻辑、统一用async/await写法即可
内容的提问来源于stack exchange,提问作者coder
相关产品推荐
相关产品推荐

