swift - 为什么我的 DispatchGroup 的等待总是超时?

标签 swift multithreading grand-central-dispatch

我有这个代码:

let goAheadAction = UIAlertAction(title: "Go Ahead", style: .default) { _ in
    let dispatchGroup = DispatchGroup()
    dispatchGroup.enter()

    Server.POSTRequest(accessPoint: "/UserServices/forgotPassword", params:["emailAddress" : self.emailAddressTextField.text!], handler: {
            (data: Data?, response: URLResponse?, error: Error?) -> Void in
        // TODO: Only considered successful case
        /*let confirmAlertController = UIAlertController(title: "Email been sent", message: "Please check your mailbox", preferredStyle: UIAlertControllerStyle.alert)

        self.present(confirmAlertController, animated: true, completion: nil)*/

        dispatchGroup.leave()
    })

    let waitResult = dispatchGroup.wait(timeout: DispatchTime(uptimeNanoseconds: 10000000000))

    if waitResult == .success {
        let emailSentAlertController = UIAlertController(title: "Email Sent", message: "We've sent an password reset link to \"\(self.emailAddressTextField.text!)\"", preferredStyle: UIAlertControllerStyle.alert)

        let gotItAction = UIAlertAction(title: "Got it", style: UIAlertActionStyle.cancel, handler: nil)

        emailSentAlertController.addAction(gotItAction)
        self.present(emailSentAlertController, animated: true, completion: nil)
    } else {
        let timeOutAlertController = UIAlertController(title: "Time Out", message: "Failed to request service, reason: \"Time Out\"", preferredStyle: UIAlertControllerStyle.alert)

        let gotItAction = UIAlertAction(title: "Got it", style: UIAlertActionStyle.cancel, handler: nil)

        timeOutAlertController.addAction(gotItAction)
        self.present(timeOutAlertController, animated: true, completion: nil)
    }
}

每次让dispatchGroup等待,总是返回timeout。怎么了?我将它设置为 10000000000 纳秒,应该是 10 秒吧?我的请求确实在 10 秒前得到了回复。

最佳答案

几个想法:

  1. 正如 rmaddy 所说,如果你想等待 10 秒,正确的语法是:

     let waitResult = dispatchGroup.wait(timeout: .now() + 10)
    

    你的语法根本不会等待,而且看起来会立即超时。

  2. 你根本不应该等待。

    首先,你应该永远阻塞主线程。在最坏的情况下,如果您在错误的时间执行此操作,您的应用程序可能会被看门狗进程杀死。充其量,这是一个可怕的用户体验(即应用程序对最终用户来说似乎完全卡住)。

    其次,根据 POSTRequest 的实现方式,您可能会引入新问题。具体来说,如果 POSTRequest 在主队列上调用其完成处理程序(例如,Alamofire 等网络库的默认行为),您可能会死锁,直到主线程上的 wait 超时到期.如果你要等待(无论如何你都不应该这样做),你要确保你永远不会在你的完成处理程序将使用的同一个队列上等待。

    请注意,如果完成处理程序不在您正在等待的同一个队列上运行,则不会有死锁风险,但您仍然会遇到我之前提到的问题,即您正在阻塞主线程,这应始终避免。

您应该改为将所有代码移入闭包内。如果该闭包在后台线程上运行,请确保 DispatchQueue.main.async { ... } 将其分派(dispatch)回主队列。

因此,正确的模式类似于以下内容。

  1. 如果您希望您的请求在 10 秒后超时,请将其构建到原始请求中。注意 timeoutInterval:

     class func POSTRequest(accessPoint: String, params: [String: String]?, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) {
         let url = URL(string: baseURLString)!
             .appendingPathComponent(accessPoint)
    
         let request = URLRequest(url: url, timeoutInterval: 10)
    
         URLSession.shared.dataTask(with: request, completionHandler: completionHandler)
     }
    

    很明显,您可能正在 POSTRequest 中做其他事情,所以不要迷失在那里的细节。关键点是将 timeoutInterval 构建到请求中。

  2. 然后,确保您的 UIAlertAction 启动请求并将所有处理简单地放在 POSTRequest 之后的 completionHandler 中,从而消除了任何调度组等待或类似的事情的需要:

     let goAheadAction = UIAlertAction(title: "Go Ahead", style: .default) { _ in
         let email = self.emailAddressTextField.text!
         let parameters = ["emailAddress": email]
         Server.POSTRequest(accessPoint: "/UserServices/forgotPassword", params: parameters) { data, response, error in
             DispatchQueue.main.async {
                 guard error == nil else {
                     let alert = UIAlertController(title: "Time Out", message: "Failed to request service, reason: \"Time Out\"", preferredStyle: .alert)
                     alert.addAction( UIAlertAction(title: "Got it", style: .cancel))
                     self.present(alert, animated: true)
                     return
                 }
    
                 let alert = UIAlertController(title: "Email Sent", message: "We've sent an password reset link to \"\(email)\"", preferredStyle: .alert)
                 alert.addAction(UIAlertAction(title: "Got it", style: .cancel))
                 self.present(alert, animated: true)
             }
         }
     }
    

    这样,没有线程被阻塞。

关于swift - 为什么我的 DispatchGroup 的等待总是超时?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/46008848/

相关文章:

java - 进程间同步?

java - 并行播放相同的声音两次

ios - 带有解析查询的 Swift 调度组

ios - 串行队列上的 dispatch_async 和 dispatch_sync 之间的区别?

ios - 如何水平居中对齐标签以快速适应所有屏幕尺寸

ios - 使用 Firebase 计算相同的操作 (swift)

java - 多线程——MySQL java connector prepared statement使用

ios - 什么时候执行方法返回的队列同步任务?

ios - 旋转图像变得扭曲和模糊?

ios - Firebase iOS SDK - 缺少数据库 URL