Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why my DispatchGroup's wait always timeout?

I have this code:

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

Every time I let dispatchGroup to wait, it always return timeout. What's wrong? I set it to 10000000000 nano sec, which should be 10 sec right? And my request did certainly get back before 10 seconds.

like image 831
user3489985 Avatar asked Sep 01 '17 22:09

user3489985


People also ask

Why am I getting a time-out error when logging in?

In order to do this: Try logging in again to see if the problem is resolved If Content Filtering is enabled, it may cause a time-out error. To change this setting:

Why am I getting a time-out error with content filtering?

If Content Filtering is enabled, it may cause a time-out error. To change this setting: These first two settings should fix the issue, but if you find the errors continue, try the following:

How to synchronize multiple work items in a group?

You attach multiple work items to a group and schedule them for asynchronous execution on the same queue or different queues. When all work items finish executing, the group executes its completion handler. You can also wait synchronously for all tasks in the group to finish executing. Creates a new group to which you can assign block objects.

How to fix PING request timed out error?

When you encounter the “ping request timed out” error, you should change the IP address in your ping command and check whether it works properly. You can try ping another website or the localhost.


2 Answers

A couple of thoughts:

  1. As rmaddy said, if you want to wait 10 seconds, the correct syntax would be:

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

    Your syntax is not going to wait at all, and it's going to look like it instantly timed out.

  2. You shouldn't be waiting at all.

    First, you should never block the main thread. At worst, you could have your app killed by the watchdog process if you do this at the wrong time. At best, it's a horrible UX (i.e. the app will appear to be completely frozen to the end user).

    Second, depending upon how POSTRequest was implemented, you might introduce a new problem. Specifically, if POSTRequest calls its completion handler on the main queue (e.g. the default behavior for network libraries like Alamofire), you could deadlock until with wait timeout on the main thread expires. If you are going to wait (which you shouldn't do anyway), you want to make sure you never wait on the same queue that your completion handler will use.

    Note, if the completion handler isn't running on the same queue that you are waiting, there is no deadlock risk, but you still suffer from the problem I mentioned earlier, namely that you're blocking the main thread, which should always be avoided.

You should instead move all that code inside the closure. And if that closure is running on a background thread, make sure to DispatchQueue.main.async { ... } to dispatch it back to the main queue.

So, the correct pattern is something like the following.

  1. If you want your request to timeout after 10 seconds, build that into the original request. Note the 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)
     }
    

    Clearly, you may be doing something else in POSTRequest, so don't get lost in the details there. The key point is to build the timeoutInterval into the request.

  2. Then, make sure your UIAlertAction starts the request and simply puts all of the processing after the POSTRequest inside its completionHandler, thereby eliminating the need for any dispatch group waiting or anything like that:

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

    This way, no threads are blocked.

like image 97
Rob Avatar answered Oct 04 '22 18:10

Rob


Your issue appears to be with your use of DispatchTime(uptimeNanoseconds:).

From its documentation:

Creates a time relative to the system clock that ticks since boot.

You want a time relative to "now", not when the system clock was last booted.

DispatchTime provides now for this purpose.

Change the line:

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

to:

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

This will wait 10 seconds from "now".

This can be shorted to:

let waitResult = dispatchGroup.wait(timeout: .now() + 10)
like image 30
rmaddy Avatar answered Oct 04 '22 16:10

rmaddy