简体   繁体   中英

GCD serial queue like approach using swift async/await api?

I am adopting the new async/await Swift API. Things work great. Currently in my apps I am using GCD serial queues as a pipeline to enforce tasks to happen serially.

For manipulating data of some critical models in the app I use a serial queue accessible from anywhere in the app:

let modelQueue = DispatchQueue(label: "com.myapp.updatemodelqueue")

Anytime a model needs to modify some model data I dispatch to that queue:

modelQueue.async {
        // ... model updates
}

With the new async/await changes I am making I still want to force model actual updates to happen serially. So, for example, when I import new model data from the server I want to make sure they happen serially.

For example, I may have a call in my model that looks like this:

func updateCarModel() async {
    
    let data = await getModelFromServer()
    
    modelQueue.async {
        
        // update model
        
    }

}

Writing that function using that pattern however would not wait for the model update changes because of the modelQueue.async . I do not want to use modelQueue.sync to avoid deadlocks.

So then after watching WWDC videos and looking at documentation I implemented this way, leveraging withCheckedContinuation :

func updateCarModel() async {
    
    let data = await getModelFromServer()
    
    
    await withCheckedContinuation({ continuation in
        modelQueue.async {
            
            // update model
         
            continuation.resume()
        }
    })
    
}

However, to my understanding withCheckedContinuation is really meant to allow us to incrementally transition to fully adopt the new async/await Swift API. So, it does not seem to be what I should use as a final approach.

I then looked into actor , but I am not sure how that would allow me to serialize any model work I want to serialize around the app like I did with a static queue like shown above.

So, how can I enforce my model around the app to do model updates serially like I used to while also fully adopting the new await/async swift API without using withCheckedContinuation ?

By making the model an actor , Swift synchronizes access to it' shared mutable state. If the model is written like this:

actor Model {
    var data: Data
    
    func updateModel(newData: Data) {
        data = newData
    }
}

The updateModel function here is synchronous, it's execution is uninterrupted after it's invoked. Because Model is an actor, Swift restricts you to treat it as if you are calling an asynchronous funtion from the outside. You'd have to await , which results in suspension of your active thread.

If in case you'd want to make updateModel async, the code within will always be synchronous unless if you explicitly suspend it by calling await. The order of execution of multiple updateModel calls is not very deterministic. As far as you don't suspend within the updateModel block, it is sure that they execute serially. In such case, there is no use making the updateModel async.

If your update model code is synchronous you can make your model actor type to synchronize access. Actors in swift behave similar to serial DispatchQueue, they perform only one task at a time in the order of submission. However, current swift actors are re-entrant, which means if you are calling any async method actor suspends the current task until the async function completes and proceeds to process other submitted tasks.

If your update code is asynchronous, using an actor might introduce data race. To avoid this, you can wait for non-reentrant actor support in swift. Or you can try this workaround TaskQueue I have created to synchronize between asynchronous tasks or use other synchronization mechanisms that are also created by me .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM