'Is it possible to pass an async function to a Core Data Managed Object Context in Swift? If not, then why?
I've looked around for a way to pass an async function to a Core Data managed object context but can't find anyway yet. I suspect there is some part of the new async/await concurrency model that I am not understanding but I have no idea what it is.
What I'd like to do:
// Grab an moc
let moc = container.newBackgroundContext()
// Enter an async context
Task {
await moc.perform {
// Get some object
let obj = moc.object(with: anObjectID)
// This is not possible because NSManagedObjectContext.perform only accepts
// a synchronous block
await obj.doSomeLongRunningProcess()
}
}
It seems odd to me that this is not possible. I'm not sure if it's just not there yet in the Core Data api because async/await is so new, or if there is a very good reason it's not possible?
Wrapping the doSomeLongRunningProcess in a Task like so
await moc.perform {
Task {
let obj = moc.object(with: anObjectID)
await obj.doSomeLongRunningProcess()
}
}
Doesn't work because the inner Task gets run on a different thread and you end up with CoreData inconsistencies. I was kinda hoping it would inherit the context's thread but this is not the case.
I'd love a way to pass async functions to an ManagedObjectContext, but failing that, I'd like to know why it doesn't/can't work?
Solution 1:[1]
You cannot directly call an async method from within perform. It needs to be synchronous method, which you would not await.
You asked:
... but what if I want to call some other async function from within my
doSomeLongRunningProcess?
The problem is that if you hit a hypothetical await suspension point inside perform, while path of execution would suspend, the thread would be free to execute other code pending on that executor, and the “continuation” (the code after the suspension point) would run later. (An await call is not like a GCD sync call, but rather more like a dispatch group notify of the continuation.) If that happens inside perform, the integrity of this task theoretically could be undermined by other tasks that could slip in before the continuation has a chance to run. For details on these suspension points, continuations, etc., see Swift concurrency: Behind the scenes
I would suggest changing doSomeLongRunningProcess to not be async, and, if possible, instead initiate any asynchronous tasks with Task { … } from within doSomeLongRunningProcess. But you can’t have this method be async and have await suspension points.
For more information about using the new async-await patterns in conjunction with Swift concurrency, see WWDC 2021 video Bring Core Data concurrency to Swift and SwiftUI.
Solution 2:[2]
I've spent a while trying to work this out. Best I can figure is that it is currently impossible given the current implementation of Swift Concurrency. Rob's answer is still basically correct but if you want some more info:
There is a similar question someone has asked over on the Swift forums How to create an object graph with serialised access using Swift concurrency which goes into detail.
You can't guarantee that an async function will run on a given executor (yet). Global Actors make it sort of possible, but it limits your model to a single Actor. This means you can't have one entity calling an async function on the main thread, and another calling it on a background thread.
Even if you could use an Actor to guarantee serialised access, everything goes out the window when you hit an await suspension point at which point the execution will move.
As far as I can tell, Apple wouldn't even be able to change NSManagedObjectContext to be an Actor as you still can't guarantee that async functions handed to it don't hit an await suspension point. Something fundamental would have to be added into the Swift Concurrency spec.
My Solution: I have basically removed any async/await methods that interact with NSManagedObjects. I'll reassess when/if Swift Concurrency allows for custom executors or the like.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | JeremySom |
