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

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

问题描述:

我有这样的代码:为什么我的DispatchGroup的等待总是超时?

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秒前肯定会回来。

+0

与您的直接问题无关,但请注意,如果您致电等待时间超时,这并不意味着您的帖子超时并且帖子失败。该帖子可能在11秒后完成,但您会告诉用户失败。 – rmaddy

一对夫妇的想法:

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

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

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

  2. 你不应该等待。

    首先,你应该从来没有块主线程。最糟糕的是,如果你在错误的时间这样做,你可能会让你的应用程序被看门狗程序杀死。充其量,这是一个可怕的UX(即应用程序似乎完全冻结给最终用户)。

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

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

你应该改变闭合内的所有代码。如果该闭包在后台线程上运行,请确保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) 
         } 
        } 
    } 
    

    这方式,没有线程被阻止。

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

从它的文档:

创建相对于开机以来蜱系统时钟的时间。

你想要一个相对于“现在”的时间,而不是系统时钟最后一次启动的时间。为此目的提供now

更改行:

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

到:

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

这将等待10秒,从 “现在”。

这可以短到:

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