Re: I think I screwed up Core Data multi-threading rules.
Re: I think I screwed up Core Data multi-threading rules.
- Subject: Re: I think I screwed up Core Data multi-threading rules.
- From: Daryle Walker <email@hidden>
- Date: Thu, 23 Feb 2017 21:25:06 -0500
> On Feb 23, 2017, at 1:56 AM, Daryle Walker <email@hidden> wrote:
>
> I naively thought this was OK, multi-threading wise. It worked before I added the “canAsynchronusly…” method to say TRUE for my main file type. When that method is added, the save hangs the program.
>
>> override func data(ofType typeName: String) throws -> Data {
>> guard typeName == Names.internationalEmailMessageUTI else { throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) }
>>
>> // Save the current message data to the store so a background task can see it.
>> var messageID: NSManagedObjectID?
>> var savingError: Error?
>> let mainContext = self.container.viewContext
>> mainContext.performAndWait {
>> do {
>> try mainContext.save()
>> } catch {
>> savingError = error
>> }
>> messageID = self.message?.objectID
>> }
>>
>> // Let the main interface continue.
>> self.unblockUserInteraction()
>> guard savingError == nil else { throw savingError! }
>> guard messageID != nil else { throw NSError(domain: NSCocoaErrorDomain, code: NSCoreDataError, userInfo: nil) }
>>
>> // Write out the message data for externalization.
>> var result: Data?
>> let backgroundContext = self.container.newBackgroundContext()
>> backgroundContext.performAndWait {
>> let backgroundMessage = backgroundContext.object(with: messageID!) as! RawMessage
>> result = backgroundMessage.messageAsExternalData
>> }
>> return result!
>> }
>
> I use the original and the background copy of the message only within each of their respective contexts. Is the new persistent-container class not thread-safe even for returning (new) contexts? Am I calling “unblockUserInteraction” inappropriately? (I had that question in another post.) Is it the way I handle throwing?
It seems that “data(ofType:)” REALLY means it when the main interface is blocked until “unblockUserInteraction()” is called. The whole method is called in an alternate thread, which means that any of your setup code (i.e. make an independent copy) that MUST be on the main thread has to be called somewhere else.
Fortunately, there is a somewhere else:
> override func save(to url: URL, ofType typeName: String, for saveOperation: NSSaveOperationType, completionHandler: @escaping (Error?) -> Void) {
> // Save the message data to the store so any background contexts can read the data later.
> do {
> try self.container.viewContext.save()
> } catch {
> completionHandler(error)
> return
> }
>
> // Do the usual code, possibly even use a background thread.
> super.save(to: url, ofType: typeName, for: saveOperation, completionHandler: completionHandler)
> }
I found out about this method from the Document guide docs. I have two questions about it.
1. Is it OK to abort your override before the call to super if your pre-super code gets an error?
2. What should happen if your post-super code (which I don’t have in my example) has an error? Do you drop it to oblivion? Or can you call the completion handler? Note that if the call to super has its own errors, then your post-super code would make the handler be called twice.
Now I can keep all the save code in the same thread:
> override func canAsynchronouslyWrite(to url: URL, ofType typeName: String, for saveOperation: NSSaveOperationType) -> Bool {
> switch typeName {
> case Names.internationalEmailMessageUTI:
> return true
> default:
> return super.canAsynchronouslyWrite(to: url, ofType: typeName, for: saveOperation)
> }
> }
>
> override func data(ofType typeName: String) throws -> Data {
> guard typeName == Names.internationalEmailMessageUTI else { throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil) }
>
> // Note the message's ID so a background context can find it later.
> let messageID: NSManagedObjectID = self.message.objectID
> self.unblockUserInteraction()
>
> // Write out the message data for externalization.
> var messageData: Data?
> let backgroundContext = self.container.newBackgroundContext()
> backgroundContext.performAndWait {
> let backgroundMessage = backgroundContext.object(with: messageID) as! RawMessage
> messageData = backgroundMessage.messageAsExternalData
> }
> return messageData!
> }
I really hope “NSManagedObject.objectID" is a thread-safe property getter.
—
Daryle Walker
Mac, Internet, and Video Game Junkie
darylew AT mac DOT com
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden