1

I am using the iOS15 Swift + Core Data code for async/await, and while using private queue concurrency, I am trying to call a await perform block inside another await perform block:

await privateMainContext.perform({
    
    var displayName: String? = nil
    await self.managedObjectContext?.perform {
        displayName = self.displayName
    }
    // ... use displayName for privateContext object
})

But I get an error with the compiler that says:

Cannot pass function of type '() async -> ()' to parameter expecting synchronous function type

If I remove the await from the inner perform call, it works OK, but doesn't that mean the self.managedObjectContext will call that in a block that might not execute before I need to use the displayName property? With old Objective-C code, I would call performBlockAndWait within another context's performBlockAndWait block and it worked fine.

Z S
  • 7,039
  • 12
  • 53
  • 105

1 Answers1

0

The fundamental problem here is that the "perform" function is declared like this:

func perform<T>(schedule: NSManagedObjectContext.ScheduledTaskType, 
                 _ block: @escaping () throws -> T) async rethrows -> T

And the relevant part to your question is block: @escaping () throws -> T. It takes a block that has to be synchronous code (the code in the block can't be suspended).

If it could suspend it would be:

block: @escaping () async throws -> T

(note the addition of async)

To solve the problem your block has to be synchronous code. That's what happens when you remove the await as you've noticed. Now you're going to be calling the perform method of the managed context whose Swift representation is:

func perform(_ block: @escaping () -> Void)

which is a synchronous call, but it runs its block asyncronously using GCD and you are right to be concerned.

The only synchronous call that also runs the block synchronously is performAndWait. So to solve your problem you can use:

await privateMainContext.perform({
    
    var displayName: String? = nil
    self.managedObjectContext?.performAndWait {
        displayName = self.displayName
    }
    // ... use displayName for privateContext object
})

The only goal no served by this code is the apparent desire to convert all this code to use Swift Concurrency to the exclusion of any GCD. But there are plenty of examples where the GCD and Swift concurrency models are not equivalent. For example, it's straightforward to create a bunch of blocks and ensure they run one after the other by putting them into a serial queue. It's not easy to create plan a bunch of Tasks and coordinate them so that they run sequentially.

Scott Thompson
  • 22,629
  • 4
  • 32
  • 34