After realizing that performBackgroundTask allows for merge conflicts thank to this question: CoreData concurrency background saving
I decided to re-look at my app to figure out where I’m getting the merge conflicts. And sure enough, I’ve been able to isolate a few glaring race conditions related to saving on background threads.
The question I linked has been floated around as the definitive answer in a few circles I’ve explored.
You essentially create an OperationQueue, set it’s maxConcurrentOperationCount to 1 and then run all context calls within the operation.
Example:
queue.addOperation {
let context = self.container.newBackgroundContext()
context.performAndWait {
block(context)
try? context.save()
}
}
But how to use native Swift concurrency?
I’ve thought about using AsyncChannel from AsyncAlgorithms to handle sending operations in and supporting back pressure (so that the sender can know when the task it sent is completed)
OperationQueue as far as I’m aware does not support native async await functionality.
I had an idea to build the above but within withCheckedContinuation like this:
await withCheckedContinuation { continuation in
queue.addOperation {
let context = self.container.newBackgroundContext()
context.performAndWait {
block(context)
try? context.save()
}
continuation.resume(returning: ())
}
}
This idea essentially locks each context call inside a queue, which when it completes returns the continuation.
First of all, I’m worried that I might be missing something with the possibilities around what is happening. Second, I’m not actually totally confident that I’ve created a serialized async call. I need to do more testing on this obviously..
edit:
I am currently using the new concurrency apis for “perform”
However, I’m seeing race conditions still
let context = PersistenceController.shared.container.newBackgroundContext()
let newEntry: DisplayOnlyEntry = try await context.perform {
let object = CDEntryObject(entry: unsavedEntry, context: context)
try context.save()
return try DisplayOnlyEntry(object: object)
}
I’m calling newBackgroundContext() often. Do I get what I’m looking for for free as long as I use a single context..
eg static let backgroundContext = **
and then always call
backgroundContext.perform { *** }
What then do I make of the new ScheduledTaskType
Do I need to make sure to call it with enqueued? If I call two tasks at the same time to save to save the same object, how to I make sure one is serially saved?




