Search code examples
swiftdispatch

How to do not dispatch so often?


I'v read a lot how dispatch work. But i Still a little confusing about it.

For example, what if I have

class ViewController: UIViewController {

    @IBAction func actionDoStuff(_ sender: UIButton) {

         DispatchQueue.global(qos: .userInitiated).async {

              Api.request { result in

                  //completes in main thread

                  //what if I need to dispatch again ?

                 DispatchQueue.global(qos: .userInitiated).async {

                     //Do other stuff here

                 }

              }

          }


     }
}

And

class Api {

    static func request(completion: @escaping (Result<String, NSError>) -> Void) {

        DispatchQueue.global(qos: .userInitiated).async {

        //url session configure
        let url = URL(fileURLWithPath: "test.com")
        URLSession.shared.dataTask(with: url) { data, response, error in

                DispatchQueue.main.async {

                     completion(.success("Request are success")) //without error handler for simplifier

                }

            }.resume()

        }


    }


}

So what we have is that we have ViewController action. When we start action we dispatch to global queue and make Api request.

1 At that point (let it be point 1) does queue gathered thread for the queue ?

Then we make Api.request call which make another dispatch to global queue.

2 Does it queued to same queue as at point 1 ? or it queue to the another queue with the same QoS ? or CPU make decision by itself ? And does it create new Thread ? Actually I know that GCD make decision to create thread by itself but I don't make sense

Then Api calls completes and by some circumstances we dispatch to the main queue

Then we dispatch again to do "//Do other stuff here" and does it creates new queue ? or new Thread ?

Also I know that we have GCD thread pool limit by 64. Thats why I'm afraid of. Also I've seen wwdc talks about Thread explosion but doesn't understand so if we often dispatch from queue to queue does it danger to get thread explosion ?

I understand that create new Thread for queue is expensive, and that we do not need often dispatch from queue to queue because we waste time to dispatch.

But what about my example is it wrong dispatch like this ?


Solution

  • Basically your understanding of queues are wrong, iOS provides by default several dispatch queues (The only difference between these queues are Quality Of Service they guarantee) and 1 serial queue (which is obviously main queue)to each app.

    Of course you can create your own serial and dispatch queues, but because your code is using dispatch.global am gonna stick with only global queues here.

    They are available all the time no matter whether your app access it or not. Thats why they are Called global queues :D

    Quoting apple

    The system provides each application with four concurrent dispatch queues. These queues are global to the application and are differentiated only by their priority level. Because they are global, you do not create them explicitly.

    These are non reference counted objects available to all the apps, so your first question of "call which make another dispatch to global queue." is illogical. Queues are not created when you access DispatchQueue.global(qos: rather you only access one of the several Dispatch Queue already available in system and add your task to it based on the QoS you are opting for.

    Lets answer your questions,

    1 At that point (let it be point 1) does queue gathered thread for the queue ?

    There is no way to guess whether Queue already has a thread or not, these are global queues, and threads are created, handled and managed by queues themselves. So if Queue already had a scheduled task to execute, or if it was already executing a task it might be having thread, else it might not. How many thread? Again we don't have control over it, Dispatch queue decides how many thread it needs to concurrently execute tasks.

    2 Does it queued to same queue as at point 1 ? or it queue to the another queue with the same QoS ? or CPU make decision by itself ? And does it create new Thread ? Actually I know that GCD make decision to create thread by itself but I don't make sense

    You are accessing the global dispatch queue with same QoS, userInitiated so obviously you added the task to same queue which you used in Point1. I hope by now you have figured out that you don't create queue when you access DispatchQueue.global(qos: rather you simply use one of the many dispatch queues provided by iOS.

    Actually I know that GCD make decision to create thread by itself but I don't make sense

    To be really honest, you don't have to that's the whole point of logic abstraction, they have written an interface called GCD api to hide the complications of low level api such as creating thread, managing and scheduling it

    Issues in your code:

    Clearly

    static func request(completion: @escaping (Result<String, NSError>) -> Void) {
    
            DispatchQueue.global(qos: .userInitiated).async {
    

    dispatches API call on .userInitiated queue, hence initial

     DispatchQueue.global(qos: .userInitiated).async {
    
                  Api.request { result in
    

    makes no sense. Thread context switching is costly and should be done only when it makes sense. Though it won't make switch again at the encounter of second statement, but initial switch was completely useless anyway

    DispatchQueue.global(qos: .userInitiated).async {

    As per Apple docs

    User-initiated tasks are second only to user-interactive tasks in their priority on the system. Assign this class to tasks that provide immediate results for something the user is doing, or that would prevent the user from using your app. For example, you might use this quality-of-service class to load the content of an email that you want to display to the user.

    Clearly your usage of .userInitiated queue to dispatch all your api call is, for the lack of better words I call abuse of userInitiated dispatch queue. User-initiated tasks are second only to user-interactive tasks in their priority on the system. do you really want all your long API call to have that priority. Thats very steep down the hill road you are taking if you ask me :)

    What should I use?

    Depends on your need, if its simple API call you might use default global queue, if you need to run your API on background configuration you might use background queue, clearly not .userInitiated to do all your laundry work.

    Hope it helps