使用withTaskGroup调用CLGeocoder时任务组异常停滞问题求助
地址搜索并发请求停滞问题排查与解决
问题描述
需要实现基于用户输入的地址搜索功能:先用MKLocalSearchCompleter获取地址建议,再通过CLGeocoder.geocodeAddressString(_:)方法根据建议标题获取地标。为优化异步请求速度,尝试用withThrowingTaskGroup并发执行多个请求,但任务组无报错却停滞,仅首个任务完成,后续任务一直挂起。该问题在Playground中隔离测试也可复现。
测试代码
import CoreLocation import MapKit class MapManager: NSObject, MKLocalSearchCompleterDelegate { let geocoder = CLGeocoder() private lazy var localSearchCompleter: MKLocalSearchCompleter = { let completer = MKLocalSearchCompleter() completer.delegate = self return completer }() func searchAddress(_ query: String) { localSearchCompleter.queryFragment = query } func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { Task { let placemarks = try! await withThrowingTaskGroup(of: [CLPlacemark].self) { group in var placemarks: [CLPlacemark] = [] print(completer.results.count) // 15 for result in completer.results { print("Task was added to the group") group.addTask { try! await self.geocoder.geocodeAddressString(result.title) } } for try await placemark in group { print("Task appended to a list") placemarks.append(contentsOf: placemark) } // This is never called return placemarks } print(placemarks) } } } let manager = MapManager() manager.searchAddress("wall street")
控制台输出
15 Task was added to the group Task was added to the group Task was added to the group Task was added to the group Task was added to the group Task was added to the group Task was added to the group Task was added to the group Task was added to the group Task was added to the group Task was added to the group Task was added to the group Task was added to the group Task was added to the group Task was added to the group Task appended to a list
问题原因
问题核心在于CLGeocoder的内部限制:同一个CLGeocoder实例同一时间只能处理一个地理编码请求。它底层使用串行队列处理任务,当通过TaskGroup并发提交多个请求时,第一个请求会占用实例,后续请求会被无限阻塞,导致任务组无法收集完所有结果,最终停滞。
解决方案
方案1:改用MKLocalSearch获取地标(推荐)
MKLocalSearch支持并发请求,且可直接基于MKLocalSearchCompletion结果发起搜索,无需通过字符串重新编码,结果更精准、效率更高。修改代码如下:
import CoreLocation import MapKit class MapManager: NSObject, MKLocalSearchCompleterDelegate { private lazy var localSearchCompleter: MKLocalSearchCompleter = { let completer = MKLocalSearchCompleter() completer.delegate = self return completer }() func searchAddress(_ query: String) { localSearchCompleter.queryFragment = query } func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { Task { let placemarks = try! await withThrowingTaskGroup(of: [CLPlacemark].self) { group in var placemarks: [CLPlacemark] = [] print(completer.results.count) for result in completer.results { print("Task was added to the group") // 基于MKLocalSearchCompletion创建搜索请求 group.addTask { let request = MKLocalSearch.Request(completion: result) let search = MKLocalSearch(request: request) let response = try await search.start() return response.mapItems.compactMap { $0.placemark } } } for try await placemarkBatch in group { print("Task appended to a list") placemarks.append(contentsOf: placemarkBatch) } print(placemarks) return placemarks } } } } let manager = MapManager() manager.searchAddress("wall street")
方案2:串行执行CLGeocoder请求(不推荐)
如果必须使用CLGeocoder,只能放弃并发,改用串行执行请求(但会回到逐个调用的慢速度):
func completerDidUpdateResults(_ completer: MKLocalSearchCompleter) { Task { var placemarks: [CLPlacemark] = [] for result in completer.results { do { let batch = try await geocoder.geocodeAddressString(result.title) placemarks.append(contentsOf: batch) print("Task completed") } catch { print("Geocode failed: \(error)") } } print(placemarks) } }
内容的提问来源于stack exchange,提问作者Mantas Skeiverys
相关产品推荐
相关产品推荐

