swiftgrand-central-dispatchdispatch-asyncdispatch-queue

Swift serial DispatchQueue not executing serially


Hey in my code the function "sendRequest" is called multiple times which sends a request to a server. The server processes all requests one after the other and sends back a multiple responses. Now I want to process these responses in the function "handleResponse", but in a serial queue. So handleResponse will only be called once at the same time. But now I have the case that my logs look like this:

[15.05.2023 11:28:00.711] DEBUG NetworkDataHandler:52 - start handleResponse
[15.05.2023 11:28:00.710] DEBUG NetworkDataHandler:52 - start handleResponse
[15.05.2023 11:28:00.713] DEBUG NetworkDataHandler:52 - start handleResponse
[15.05.2023 11:28:00.713] DEBUG NetworkDataHandler:52 - start handleResponse
[15.05.2023 11:28:00.714] DEBUG NetworkDataHandler:52 - start handleResponse
...

handleResponse is thus called several times, although it has not yet been completed. How can I get the callback to work serially?

Simplified code:

func sendRequest() { // is called several times
    applicationNetworkManager().sendQuery(
        ...
        successHandler: { response in
 
            let workItem = DispatchWorkItem {
                self!.handleResponse(response: response)
            }
            let serialQueue = DispatchQueue(label: "serialQueue", qos: .background)
            serialQueue.async(execute: workItem)
        }
    )
}

func handleResponse(response: Response) {
    Logger.d("start handleResponse")

    // do some stuff and write response data to local storage

    DispatchQueue.main.async {
        // update ui
    }

    Logger.d("end handleResponse")
}



Solution

  • In sendRequest you are creating a new dispatch queue each time you call let serialQueue = DispatchQueue(label: "serialQueue", qos: .background). Even though all of the queue instances have the same label, they are different queues.

    You don't show how you create the class that contains sendRequest, so I am not sure if there is one instance of this class or if you have multiple instances.

    It is also best to avoid the .background QoS; This QoS level can be starved for CPU in a low power situation. .utility is a good choice.

    Assuming you have one instance you would use something like:

    class NetworkHandler {
    
        let myQueue: DispatchQueue
    
        init() {
            self.myQueue = DispatchQueue(label:"serialQueue", qos: .utility)
        }
    }
    

    You can then use this queue in your handler.