PaFi
PaFi

Reputation: 920

Save multiple async contexts with core data, data missing

When the app starts, I want to update my current database. Sometimes not all the data gets saved and some data is missing.

I am doing the save with this function:

func save1() {
        let group = DispatchGroup()

        for a in save.oldVersion.defaExchangesEinzeln {
            if save.checkForVersionExchange(exchange: a.key) {
                group.enter()
                save.saveVersionExchanges(exchange: a.key)
                save.storeContainer.performBackgroundTask{ context in
                    self.save.saveDefAExchange(managedContext: context, exchange: a.key)
                    try? context.save()
                    group.leave()
                }
            }
        }
        for b in save.oldVersion.fdefCoinstBtc {
            if save.checkForVersionFdefCoinsTBTC(exchange: b.key) {
                save.saveVersionFdefCoinsTBTC(exchange: b.key)
            }
        }

        for c in save.oldVersion.fdefCoinstUsd {
            if save.checkForVersionFdefCoinsTUSD(exchange: c.key) {
                save.saveVersionFdefCoinsTUSD(exchange: c.key)
            }
        }

        if save.checkForCoinNamesExall() || save.checkForVersionIconsExall() || save.checkForVersionFdefCoinsTarrExall() {
            save.saveVersionOthers()
        }

        if save.checkForCoinNamesExall() {
            group.enter()
            storeContainer?.performBackgroundTask{ context in
                self.save.saveCoinNames(managedContext: context)
                do {
                    try context.save()
                } catch let error {
                    print(error.localizedDescription)
                }
                group.leave()
            }
        }
        if save.checkForVersionIconsExall() {
            group.enter()
            storeContainer?.performBackgroundTask { context in
                self.save.saveIcons(managedContext: context)
                do {
                    try context.save()
                } catch let error {
                    print(error.localizedDescription)
                }
                group.leave()
            }
        }

        if save.checkForVersionFdefCoinsTarrExall() {
            group.enter()
            storeContainer?.performBackgroundTask { context in
                self.save.saveGroups(context)
                do {
                    try context.save()
                } catch let error {
                    print(error.localizedDescription)
                }
                group.leave()
            }
        }
        group.notify(queue: .main) {
            print("Update complete 1")
            self.delegateWebsocket?.startWebsocket()
        }
    }

I can imagine that due to the async call the contexts somehow conflict with each other. Why does it happen? What can I do to improve the code? Thanks!

Upvotes: 0

Views: 548

Answers (1)

Tom Harrington
Tom Harrington

Reputation: 70966

Using performBackgroundTask can be incredibly convenient but it has a sharp edge that's not always apparent at first. Every time you call performBackgroundTask, a new context is created, and the block you pass in executes on that context's private queue. The block is executed asynchronously.

The upshot is that if you call performBackgroundTask repeatedly in a loop, as you are, there's no guarantee that the blocks will be synchronized relative to each other. That can be a problem.

There are a couple of approaches you could take to deal with this. I'm not sure I'm following all of your code so I'm not sure which might be best. Alternatives include:

  • Wrap all of the code in a single performBackgroundTask instead of creating a bunch of background tasks. Or possibly just reduce the number of tasks by putting performBackgroundTask outside your initial loop instead of inside.
  • Drop performBackgroundTask. Instead, create a single background context and use that. Get a new context using newBackgroundContext() and keep it around. Then replace your background tasks above with perform or performAndWait calls on that context.

Upvotes: 1

Related Questions