Home Wrapping Async/Main Thread blocks around code
Reply: 1

Wrapping Async/Main Thread blocks around code

NullHypothesis
1#
NullHypothesis Published in 2017-12-06 05:35:31Z

I am writing some code to let the user tap a button to log in, and after the login is successful, immediately make another call to pull in data from the system if it's available. I'm a little confused about where I need/don't need to wrap my code in different thread blocks. Can I just put everything in DispatchQueue.main.async given that I'm doing UI work on the main thread? Here is my code flow - I was wondering if someone could review and let me know if I've got the right structure of wrapping code in async/main thread blocks.

let messageFrame = AlertCreator.progressBarDisplayer(view: self.view, message: "Loading", true)

DispatchQueue.main.async(execute: {

  //Present loading spinner while call is made
  self.view.addSubview(messageFrame)

  //Make an AUTH call using URLSession.shared.dataTask (with callback)                   
  UserSecurityService.AuthenticateUser(username: self.txtUsername.text!, password: self.txtPassword.text!)
     { (authenticationResponse) -> () in

        if (authenticationResponse.Status == ResponseCode.LOGIN_SUCCESS)
         {    
              //If this user logged in successfully, and now need to import data, then do so, otherwise just proceed to the app.                  
              if (authenticationResponse.Value!.HasDataForImport) {

                         //Make ANOTHER async call using URLSession.shared.dataTask (with callback) 
                          UserDataService.GetUserSettings()
                                { response in
                                     //Remove the spinner
                                     messageFrame.removeFromSuperview()

                                    if (response.Status == ResponseCode.OK)
                                    {
                                        //Success, go to dashboard
                                        self.presentHomeViewController()
                                    }
                                    else {
                                        //Show alert failure, with clicking 'ok' firing a callback to take the user to the dashboard
                                    }
                               }
                   }
                  else {
                      //Data does not exist, so just stop the spinner and take the user to the dashboard
                        self.presentHomeViewController()
                  }
          else if (authenticationResponse.Status == ResponseCode.INVALID_USERNAME_PASSWORD) {
                 //User entered the wrong username/password
                  messageFrame.removeFromSuperview()
                 <alert is created/presented here>            
                 }
}) //main dispatch async execute

If you noticed, I am making an async call to authenticate, that does something on the UI (shows a spinner to prevent any other activity, checks if the login was successful or not, and removes the spinner. It also potentially presents an alert, and then takes the user to a view controller.

My questions specifically are:

  1. Should I not wrap this entire block in DispatchQueue.main.async, but rather only the two web calls? I've done all kinds of combinations, most of which worked (and some that crashed), so I'm not entirely sure if I just need DispatchQueue.main.async, or need something nested inside of it such as a DispatchQueue.global(qos: DispatchQoS.QoSClass.userInteractive).async as well?
  2. Should I wrap each web call, or only the outer one
  3. Should I be wrapping the UI-related things in their own blocks, for example when I present alerts, view controllers, or even stop the spinner? 3.

Thank you for any assistance / suggestions you could provide!

Sandeep Bhandari
2#
Sandeep Bhandari Reply to 2017-12-06 06:58:49Z

I have tried adding DispatchQueue.main and DispatchQueue.global stub to your question just to show how to and when to switch between queue. This may not be a complete copy, paste solution as I dont have complete code of yours so I could not compile it. This is intended only to show how to use DispatchQueue

let messageFrame = AlertCreator.progressBarDisplayer(view: self.view, message: "Loading", true)
        //Present loading spinner while call is made
        self.view.addSubview(messageFrame)

        DispatchQueue.global(qos: .default).async {
            //Make an AUTH call using URLSession.shared.dataTask (with callback)
            UserSecurityService.AuthenticateUser(username: self.txtUsername.text!, password: self.txtPassword.text!)
            { (authenticationResponse) -> () in

                if (authenticationResponse.Status == ResponseCode.LOGIN_SUCCESS)
                {
                    //If this user logged in successfully, and now need to import data, then do so, otherwise just proceed to the app.
                    if (authenticationResponse.Value!.HasDataForImport) {

                        //Make ANOTHER async call using URLSession.shared.dataTask (with callback)
                        UserDataService.GetUserSettings()
                            { response in
                                //Remove the spinner
                                messageFrame.removeFromSuperview()

                                if (response.Status == ResponseCode.OK)
                                {
                                    DispatchQueue.main.async {
                                        //Success, go to dashboard
                                        self.presentHomeViewController()
                                    }
                                }
                                else {
                                    DispatchQueue.main.async {
                                        //Show alert failure, with clicking 'ok' firing a callback to take the user to the dashboard
                                    }
                                }
                        }
                    }
                    else {
                        DispatchQueue.main.async {
                            //Data does not exist, so just stop the spinner and take the user to the dashboard
                            self.presentHomeViewController()
                        }
                    }
                    else if (authenticationResponse.Status == ResponseCode.INVALID_USERNAME_PASSWORD) {
                        DispatchQueue.main.async {
                            //User entered the wrong username/password
                            messageFrame.removeFromSuperview()
                            <alert is created/presented here>
                        }
                    }
                }
                // final = final?.replacingOccurrences(of: "Rs.", with: "")
            }
        }

Advice (Sort of)

Should I not wrap this entire block in DispatchQueue.main.async, but rather only the two web calls? I've done all kinds of combinations, most of which worked (and some that crashed), so I'm not entirely sure if I just need DispatchQueue.main.async, or need something nested inside of it such as a DispatchQueue.global(qos: DispatchQoS.QoSClass.userInteractive).async as well?

You should never wrap web service calls in DispatchQueue.main.async you should wrap it in DispatchQueue.global(qos: .default).async I am assuming Quality Of Service (QOS) to be default which is normally the case unless you have specific requirement for background,hight or low. Thumb rule, update all your UI components on main thread. Because your main thread is associated with serialized MainQueue it is important to switch to mainQueue context.

DispatchQueue.main.async will only grab the main thread asynchronously where DispatchQueue.main.sync will try to grab main thread synchronously there by pausing the current execution of the main thread. But both will get you an access to main thread only So DispatchQueue.main.async is not a ideal way to make non UI related calls like web services.

Issue with making a long, non ui related task on main thread is that, there can only be one main thread in the entire life cycle of Application and because it gets busy with non UI related task and because Main Queue is Serialized all other ui tasks will starve in main queue waiting for main thread and hence will result in non responsive UI or at worst (best for user :P) results in iOS killing your app.

Should I wrap each web call, or only the outer one

That depends on how is your web call designed. If you use Alamofire/AFNewtorking kind of frameworks, they return the result on main thread even if you call the web service on non-ui thread. In such cases its necessary to switch to non-ui thread again. So in those scenarios you will have to wrap each web service calls in DispatchQueue.global.async else outer one should do just fine :)

In the answer I posted above assumes your web service call completion block returns the response using non-ui thread hence I have wrapped both web service calls in single DispatchQueue.global.async modify it if thats not the case.

Should I be wrapping the UI-related things in their own blocks, for example when I present alerts, view controllers, or even stop the spinner

Again depends. If the control reaches the UI component update statements on any thread other that main thread you should use DispatchQueue.main.async If you are sure your control reaches such statements always on main thread you dont need to wrap them. If you are unsure, then you can either blindly go and add a block around such statement or make it little more intelligent decision by adding a if condition to check if thread is main thread or not :)

One final advice : Context switching is not cheap so use it wisely.

Hope it helps

You need to login account before you can post.

About| Privacy statement| Terms of Service| Advertising| Contact us| Help| Sitemap|
Processed in 0.304026 second(s) , Gzip On .

© 2016 Powered by mzan.com design MATCHINFO