Swift中后台线程执行同步服务器请求的生产环境安全性问询
后台线程中阻塞式网络请求的生产环境安全性问题
问题代码
func synchronousGetRequest(group: DispatchGroup, completion: @escaping ()-> ()) { guard var url = URL(string: "https://192.168.187.30:5001/") else {return} let session = URLSession.shared.dataTask(with: url) { _, _, _ in completion() } session.resume() group.wait() } func loopFunc() { let group = DispatchGroup() for _ in 1...5 { print("start request") group.enter() synchronousGetRequest(group: group) { print("request finished") group.leave() } } } DispatchQueue.global(qos: .background).async { loopFunc() } print("End of script")
执行输出
End of script start request request finished start request request finished start request request finished start request request finished start request request finished
问题
已知阻塞线程并非良好实践,请问将上述逻辑放在后台线程执行时,是否可以安全地应用于生产环境?
回答
即使放在后台线程,这种阻塞式的写法也不适合生产环境,核心原因如下:
1. 浪费系统线程资源
DispatchQueue.global 属于系统管理的线程池,线程总量有限。每次调用 group.wait() 会直接阻塞当前后台线程,直到网络请求完成——这段时间内该线程完全无法处理其他任务,会降低应用整体的后台并发效率。如果存在大量这类阻塞操作,甚至可能耗尽线程池资源,导致其他后台任务无法正常执行。
2. 存在无限制阻塞风险
如果服务器无响应、网络超时时间过长,wait() 会一直阻塞线程,直到URLSession的默认超时触发(通常约60秒)。这段时间内线程被完全占用,没有容错或中断机制,可能引发不可控的资源占用问题。
3. 代码可维护性差
这种用阻塞模拟同步执行的写法逻辑绕弯、不直观,后续维护时容易出现 enter() 和 leave() 不匹配的bug,排查难度高。
优化方案
推荐用非阻塞方式实现串行网络请求,以下是两种适合生产环境的写法:
方案1:使用Swift async/await(Swift 5.5+)
这是现代Swift最简洁的写法,完全无阻塞:
func getRequest() async { guard let url = URL(string: "https://192.168.187.30:5001/") else { return } do { // 异步等待请求完成,不会阻塞线程 let (_, _) = try await URLSession.shared.data(from: url) } catch { // 处理请求错误 print("请求失败:\(error)") } } func loopRequests() async { for _ in 1...5 { print("start request") await getRequest() print("request finished") } } // 在后台优先级任务中执行 Task(priority: .background) { await loopRequests() } print("End of script")
方案2:使用串行队列(兼容低版本Swift)
如果需要兼容Swift 5.5以下版本,可通过串行队列实现串行请求,避免线程阻塞:
func loopRequests() { // 创建串行队列,保证请求按顺序执行 let requestQueue = DispatchQueue(label: "com.example.serialRequestQueue") for _ in 1...5 { requestQueue.async { print("start request") guard let url = URL(string: "https://192.168.187.30:5001/") else { return } let task = URLSession.shared.dataTask(with: url) { _, _, _ in print("request finished") } task.resume() } } } DispatchQueue.global(qos: .background).async { loopRequests() } print("End of script")
内容的提问来源于stack exchange,提问作者EddiKh
相关产品推荐
相关产品推荐

