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

时间:2017-09-01 22:49:30

标签: 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等待时,它总是返回超时。怎么了?我把它设置为10000000000纳秒,这应该是10秒吧?我的请求确实在10秒之前回来了。

2 个答案:

答案 0 :(得分:3)

有几点想法:

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

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

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

  2. 你根本不应该等待。

    首先,你应该从不阻止主线程。在最坏的情况下,如果您在错误的时间执行此操作,您可能会被监视程序进程杀死。充其量,它是一个可怕的用户体验(即应用程序似乎完全冻结给最终用户)。

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

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

  3. 您应该在闭包内移动所有代码。如果该闭包在后台线程上运行,请确保DispatchQueue.main.async { ... }将其发送回主队列。

    因此,正确的模式如下所示。

    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)
              }
          }
      }
      

      这样,没有线程被阻止。

答案 1 :(得分:0)

您的问题似乎与您使用DispatchTime(uptimeNanoseconds:)

有关

从其文档:

  

创建相对于自启动后滴答的系统时钟的时间。

你想要一个相对于“现在”的时间,而不是上次启动系统时钟的时间。

DispatchTime为此目的提供now

更改行:

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

为:

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

这将从“now”等待10秒。

这可以简称为:

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