Home > Enterprise >  GCD serial Queue not executing serially
GCD serial Queue not executing serially

Time:08-24

I don't understand why the code is not executing serially. I have called the closures syncronously so that the next closure will be called only after the completion of previous one. On terminal, the last closure is printed first which I was expecting to execute in the end, then the second one & then the first one at last. Any help on this ? Please do suggest the right approach to solve this as I am new to swift.

func getLocation(from address: String, completion: @escaping (_ location: CLLocationCoordinate2D?)-> Void) {
    let geocoder = CLGeocoder()
    geocoder.geocodeAddressString(address) { (placemarks, error) in
        guard let placemarks = placemarks,
        let location = placemarks.first?.location?.coordinate
        else {
            completion(nil)
            return
         }
        completion(location)
    }
}

let queue = DispatchQueue(label: "queue")
queue.sync {
    self.getLocation(from: self.sourceString) { location in
        if(location != nil){
            self.source = MKPlacemark(coordinate: location!)
            print("Source found")
         }
         else{
            print("Source not found")
         }
    }
}
queue.sync {
    self.getLocation(from: self.sourceString) { location in
        if(location != nil){
            self.destination = MKPlacemark(coordinate: location!)
            print("Destination found")
         }
         else{
            print("Destination not found")
         }
    }
}
queue.sync {
    if(self.source.coordinate.latitude != 0.0 && self.destination.coordinate.latitude != 0.0 ){
         self.bringMap = true
     }
     else{
         print("coordinates not updated yet")
     }
}

CodePudding user response:

You're synchronously performing work that is async. So the sync will block the current thread until the work in the block is done. But the work in the block just enqueues some other stuff onto another thread, and that returns immediately.

The key thing to understand is that

getLocation(from: ..., completion: ...)

is a function that returns immediately.

Yes, it takes a function as an argument, that's called the completion handler, and at some point that function may be called, but that doesn't change the fact that it returns immediately just like:

print("something")

returns immediately. It doesn't wait for the completion handler to be called.

If you need to do something when the completion handler is called your options are many, one option is to use the completion handler to call some other function which is "the next thing to do"

You can use GCD with dispatch_group(s) and dispatch_group_notify, you can use OperationQueues and (NS)Operations, or ideally you should use "structured concurrency" and the async/await version of the reverse geocoder. let coordinate = try await geocoder.geocodeAddressString(address).first?.location?.coordinate

CodePudding user response:

Shadowrun is correct, that you are initiating these various geocoded requests serially, but because they run asynchronous, each subsequent task is not waiting for the prior one to finish.

There are convoluted GCD patterns that could address this problem, but async-await of Swift concurrency would be easier. This handles dependencies between separate asynchronous tasks much more elegantly. E.g.

func lookupLocations() async throws {
    let geocoder = CLGeocoder()
    guard 
        let placemark1 = try await geocoder.geocodeAddressString(sourceString).first,
        let placemark2 = try await geocoder.geocodeAddressString(destinationString).first
    else { return }

    source = MKPlacemark(placemark: placemark1)
    destination = MKPlacemark(placemark: placemark2)
    bringMap = true
}

And, if you’re trying to start this from a non-asynchronous context, you can use Task:

Task {
    try await lookupLocations()
    print("done looking up locations")
}
  • Related