6

Many people have asked a question with similar title, but very different purpose:

CoreData requires that you keep track of your current queue, your current thread, and your current NSOperationQueue (if you're an NSOperation), if you allow method calls to come from other classes (which, by default, every class allows). There are no "maybes" about this: it's a hard requirement.

That's fine, and in general it's easy to ensure:

NSAssert( [NSThread currentThread].isMainThread || myPrivateQueue == dispatch_get_current_queue(), @"You tried to call this method from an external thread, or a queue other than my internal private queue. That's not legal, and will cause data corruption" );

...except that Apple has gone and deprecated dispatch_get_current_queue(), apparently "because people were abusing it to get around missing functionality in GCD / bits of GCD they didn't understand".

NB: my usage of dispatch_get_current_queue() above appears to be correct and non-abusive, judging by Apple's header comment: the whole point is that I'm checking that the queue is the private one I created (which Apple claims is acceptable usage).

Leaving aside the wisdom of deprecating something simply because of flaws in its implementation :( ... has anyone found a workaround for this being removed by Apple. Specifically: with CoreData, you have to track the queue - is there another way of doing that?

(this matters because: with CoreData, if you allow something to accidentally call such a method, you won't get a "crash", you'll get "data corruptioin that will show up at some point in the future when it's too late to fix it")

Adam
  • 32,900
  • 16
  • 126
  • 153
  • I've been using `dispatch_queue_set_specific()` and `dispatch_get_specific()`, for the application I asked about here: http://stackoverflow.com/questions/12806506/how-can-i-verify-that-i-am-running-on-a-given-gcd-queue-without-using-dispatch-g and it's been working well to identify particular queues. The comment by Jody on the answer mentions the use of something similar within Core Data. – Brad Larson Aug 29 '13 at 14:57
  • @BradLarson dispatch_get_specific() is very interesting, thanks. I'm not sure what happens if you call it when you're not inside a block (in correct execution, you would be, but I'd like to know what happens when it goes wrong, because that's the setup I'm trying to catch/assert/log against) – Adam Aug 29 '13 at 16:49
  • I don't know that being in a block matters. All that it tests for is a key assigned to the particular queue this code is running on. As I show there, I use this for a function that guarantees synchronous execution of code on a particular queue, either by synchronously dispatching a block to the queue or by running the code directly if already on that queue. – Brad Larson Aug 29 '13 at 17:12
  • 3
    `dispatch_get_current_queue()` was deprecated because the _design_ of the API is flawed, not the implementation. The intended replacement functionality for your usage is indeed `dispatch_get_specific()`. – das Aug 29 '13 at 23:51
  • @BradLarson if you add that as an answer, I'll mark it correct. SimpleMan's answer is good, but I think it's more of a workaround than a direct solution. – Adam Aug 30 '13 at 11:58

2 Answers2

4

performBlock always run it in correct thread. Just use:

[yourManagedObjectContext performBlock:^{
//do your stuff here
}];

You don't need track current context anymore, because your MOC knows which thread fire this MOC in first place. When you use performBlock or performBlockAndWait you are safe.

Jakub
  • 13,712
  • 17
  • 82
  • 139
  • Good idea, but it seems to have problems with thread locking and performance in complex situations - by "problems" I mean "maybe CoreData issues, probably also bugs in our codebase, possibly both together". One of my first steps was to start Asserting liberally to find out why there are occasional "CoreData hangs for tens of seconds on a mutex which appears uncontested". – Adam Aug 29 '13 at 12:45
  • ...so I'm trying to remove our reliance on 'always performBlock for everything" and find out what's actually happening/not happening – Adam Aug 29 '13 at 12:46
  • "Always `performBlock:` for everything" is pretty much the recommended way to do things AFAICT. `dispatch_get_current_queue()` is largely useless because of the way libdispatch works -- there are a small number of "global" queues and other queues eventually target operations to execute on those queues. This renders that call useless because which is the current queue? The one it was enqueued on? Or the global queue it's executing on. The enqueuer is the arbiter of what queue you're on. In this case, `performBlock:` is the enqueuer, and is the only way to know you're on that queue. – ipmcc Aug 29 '13 at 13:01
0

In my specific case, I replaced all the performBlock calls by:

  1. moving all the CoreData actions to a single class
  2. creating a private dispatch_queue
  3. merging all my internal calls into a single call
  4. the single call wraps itself in a dispatch_async( (private internal queue) )
    1. ...and the first thing it does inside the dispatch is "if( self.privateMOC == nil )" ... and creates the local MOC, guaranteeing that the MOC only ever is accessed and creatd on that one queue

Two things happened:

  1. the mutex/lock hangs have all disappeared (Although this could be coincidence - I'll report back later if it turns out the locks are still getting deadlocked)
  2. performance appears noticeably better than with performBlock: being used everywhere
    1. NOTE: we had a lot of performBlock calls, and were doing this frequently (sensitive data that needs to change the CoreData MOC contents at a moderately high frequency). I don't know if it would be a noticeable difference in a normal app

...so, for now, for my purposes: this has proved a good solution. I don't believe it's the "right" generic answer to the question, but ... I recommend it to anyone else who finds "performBlock" is less of a panacea than it appears to be.

Adam
  • 32,900
  • 16
  • 126
  • 153