如何从DispatchQueue返回Ping测试结果?Swift异步编程问题
问题解决:从SwiftyPing的DispatchQueue回调中返回测试结果
问题根源
你的代码里waitForDataFromPing调用startPinging后立刻返回self.pingResult,但SwiftyPing的finished回调是异步触发的(还嵌套了DispatchQueue.main.async),此时pingResult还未被赋值,自然拿不到正确结果。另外,全局的latArray和pingResult作为共享状态,在并发请求场景下还会出现数据混乱。
解决方案:用Continuation将回调API转为async/await风格
利用Swift的withCheckedThrowingContinuation,把SwiftyPing的回调逻辑包装成可等待的异步函数,让代码能等待ping测试完成后再返回结果,避免提前返回空值。
修改后的代码示例
替换原有的startPinging和waitForDataFromPing函数
func performPing(hostname: String, n: Int) async throws -> PingTestResult { return try await withCheckedThrowingContinuation { continuation in do { let ping = try SwiftyPing(host: hostname, configuration: PingConfiguration(interval: 1.0, with: 1), queue: DispatchQueue.global()) ping.targetCount = n var latArray: [String] = [] ping.observer = { response in var latency = String(format: "%.3f ms", response.duration * 1000) if let error = response.error { if error == .requestTimeout { latency = "Timeout \(latency)" } else { print(error) latency = error.localizedDescription } } latArray.append(latency) print("\nPing Test #\(response.trueSequenceNumber): \(latency)\n") } ping.finished = { result in DispatchQueue.main.async { let dateformatter = DateFormatter() dateformatter.dateFormat = "yyyy-MM-dd HH:mm:ss" dateformatter.timeZone = TimeZone.current let date = dateformatter.string(from: Date()) guard let roundtrip = result.roundtrip else { continuation.resume(throwing: NSError(domain: "PingError", code: -1, userInfo: [NSLocalizedDescriptionKey: "无往返数据"])) return } guard let packetLoss = result.packetLoss else { continuation.resume(throwing: NSError(domain: "PingError", code: -2, userInfo: [NSLocalizedDescriptionKey: "丢包数据不可用"])) return } let rtMin = String(format: "%.3f ms", roundtrip.minimum * 1000) let rtMax = String(format: "%.3f ms", roundtrip.maximum * 1000) let rtAvg = String(format: "%.3f ms", roundtrip.average * 1000) let rtStdDev = String(format: "%.3f ms", roundtrip.standardDeviation * 1000) // 打印统计信息 var message = "\n--- \(hostname) ping statistics ---\n" message += "\n测试时间: \(date)\n" message += "\n测试次数: \(n)\n" message += "\n发送数据包: \(result.packetsTransmitted)\n" message += "\n接收数据包: \(result.packetsReceived)\n" message += String(format: "\n丢包率: %.1f%% \n", packetLoss * 100) message += "\n往返最小延迟: \(rtMin)" message += "\n往返最大延迟: \(rtMax)" message += "\n往返平均延迟: \(rtAvg)" message += "\n往返延迟标准差: \(rtStdDev)" print(message) // 构造结果并返回 let pingResult = PingTestResult( latency: latArray, Date: date, packetsTrasmitted: result.packetsTransmitted, packetsReceived: result.packetsReceived, packetLoss: packetLoss * 100, rtMin: rtMin, rtMax: rtMax, rtAvg: rtAvg, rtStdDev: rtStdDev ) continuation.resume(returning: pingResult) } } try ping.startPinging() } catch { continuation.resume(throwing: error) } } }
修改路由接口调用
routes.post("ping") { req async throws -> PingTestResult in let pingConfig = try req.content.decode(PingConfig.self) print("hostname: \(pingConfig.host!), to be ping: \(pingConfig.n!)\n") return try await performPing(hostname: pingConfig.host!, n: pingConfig.n!) }
关键说明
- Continuation的作用:
withCheckedThrowingContinuation会暂停当前异步任务,直到调用continuation.resume(),确保等待SwiftyPing的finished回调执行完成后再返回结果。 - 移除全局状态:用局部变量
latArray存储延迟数据,避免多个请求之间的状态污染。 - 错误处理:在回调里检查必要的结果字段,缺失时抛出错误,符合
async throws的函数签名。 - 修正原代码错误:原代码中打印统计信息时重复写了“Round-trip Min”,已修正为对应字段。
内容的提问来源于stack exchange,提问作者MichaelG
相关产品推荐
相关产品推荐

