NSManagedObjectContext performBlockAndWait: doesn't execute on background thread?
Asked Answered
E

4

45

I have an NSManagedObjectContext declared like so:

- (NSManagedObjectContext *) backgroundMOC {
    if (backgroundMOC != nil) {
        return backgroundMOC;
    }
    backgroundMOC = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    return backgroundMOC;
}

Notice that it is declared with a private queue concurrency type, so its tasks should be run on a background thread. I have the following code:

-(void)testThreading
{
    /* ok */
    [self.backgroundMOC performBlock:^{
        assert(![NSThread isMainThread]); 
    }];

    /* CRASH */
    [self.backgroundMOC performBlockAndWait:^{
        assert(![NSThread isMainThread]); 
    }];
}

Why does calling performBlockAndWait execute the task on the main thread rather than background thread?

Endearment answered 6/8, 2012 at 16:7 Comment(0)
B
103

Tossing in another answer, to try an explain why performBlockAndWait will always run in the calling thread.

performBlock is completely asynchronous. It will always enqueue the block onto the queue of the receiving MOC, and then return immediately. Thus,

[moc performBlock:^{
    // Foo
}];
[moc performBlock:^{
    // Bar
}];

will place two blocks on the queue for moc. They will always execute asynchronously. Some unknown thread will pull blocks off of the queue and execute them. In addition, those blocks are wrapped within their own autorelease pool, and also they will represent a complete Core Data user event (processPendingChanges).

performBlockAndWait does NOT use the internal queue. It is a synchronous operation that executes in the context of the calling thread. Of course, it will wait until the current operations on the queue have been executed, and then that block will execute in the calling thread. This is documented (and reasserted in several WWDC presentations).

Furthermore, performBockAndWait is re-entrant, so nested calls all happen right in that calling thread.

The Core Data engineers have been very clear that the actual thread in which a queue-based MOC operation runs is not important. It's the synchronization by using the performBlock* API that's key.

So, consider 'performBlock' as "This block is being placed on a queue, to be executed at some undetermined time, in some undetermined thread. The function will return to the caller as soon as it has been enqueued"

performBlockAndWait is "This block will be executed at some undetermined time, in this exact same thread. The function will return after this code has completely executed (which will occur after the current queue associated with this MOC has drained)."

EDIT

Are you sure of "performBlockAndWait does NOT use the internal queue"? I think it does. The only difference is that performBlockAndWait will wait until the block's completion. And what do you mean by calling thread? In my understanding, [moc performBlockAndWait] and [moc performBloc] both run on its private queue (background or main). The important concept here is moc owns the queue, not the other way around. Please correct me if I am wrong. – Philip007

It is unfortunate that I phrased the answer as I did, because, taken by itself, it is incorrect. However, in the context of the original question it is correct. Specifically, when calling performBlockAndWait on a private queue, the block will execute on the thread that called the function - it will not be put on the queue and executed on the "private thread."

Now, before I even get into the details, I want to stress that depending on internal workings of libraries is very dangerous. All you should really care about is that you can never expect a specific thread to execute a block, except anything tied to the main thread. Thus, expecting a performBlockAndWait to not execute on the main thread is not advised because it will execute on the thread that called it.

performBlockAndWait uses GCD, but it also has its own layer (e.g., to prevent deadlocks). If you look at the GCD code (which is open source), you can see how synchronous calls work - and in general they synchronize with the queue and invoke the block on the thread that called the function - unless the queue is the main queue or a global queue. Also, in the WWDC talks, the Core Data engineers stress the point that performBlockAndWait will run in the calling thread.

So, when I say it does not use the internal queue, that does not mean it does not use the data structures at all. It must synchronize the call with the blocks already on the queue, and those submitted in other threads and other asynchronous calls. However, when calling performBlockAndWait it does not put the block on the queue... instead it synchronizes access and runs the submitted block on the thread that called the function.

Now, SO is not a good forum for this, because it's a bit more complex than that, especially w.r.t the main queue, and GCD global queues - but the latter is not important for Core Data.

The main point is that when you call any performBlock* or GCD function, you should not expect it to run on any particular thread (except something tied to the main thread) because queues are not threads, and only the main queue will run blocks on a specific thread.

When calling the core data performBlockAndWait the block will execute in the calling thread (but will be appropriately synchronized with everything submitted to the queue).

I hope that makes sense, though it probably just caused more confusion.

EDIT

Furthermore, you can see the unspoken implications of this, in that the way in which performBlockAndWait provides re-entrant support breaks the FIFO ordering of blocks. As an example...

[context performBlockAndWait:^{
    NSLog(@"One");
    [context performBlock:^{
        NSLog(@"Two");
    }];
    [context performBlockAndWait:^{
        NSLog(@"Three");
    }];
}];

Note that strict adherence to the FIFO guarantee of the queue would mean that the nested performBlockAndWait ("Three") would run after the asynchronous block ("Two") since it was submitted after the async block was submitted. However, that is not what happens, as it would be impossible... for the same reason a deadlock ensues with nested dispatch_sync calls. Just something to be aware of if using the synchronous version.

In general, avoid sync versions whenever possible because dispatch_sync can cause a deadlock, and any re-entrant version, like performBlockAndWait will have to make some "bad" decision to support it... like having sync versions "jump" the queue.

Borg answered 6/8, 2012 at 17:36 Comment(13)
Are you sure of "performBlockAndWait does NOT use the internal queue"? I think it does. The only difference is that performBlockAndWait will wait until the block's completion. And what do you mean by calling thread? In my understanding, [moc performBlockAndWait] and [moc performBloc] both run on its private queue (background or main). The important concept here is moc owns the queue, not the other way around. Please correct me if I am wrong.Secretary
Please see recent edits that hopefully address your concerns.Borg
Very detailed answer. Do you happen to know which year the WWDC videos you mentioned would be? I would be interested in watching them.Talkathon
@JodyHagins perfect description but just to be clear, any performBlockAndWait inside a performBlock will cause a deadlock?? as per my understanding, dispatch_sync blocks the caller thread and enqueues the block in its queue to execute at some undetermined time. Since queue is same so it will be blocked and performBlockAndWait block will never get executed.Necklace
No. Remember, Core Data uses GCD, and has their own synchronization as well... if you think about it a bit, you will see that they must do so, otherwise calls to performBlockAdnWait could not be re-entrant since nested calls to dispatch_sync will deadlock.Borg
Assume my code is just running on a background thread. I'm getting my mainContext and perform some operation using the performBlockAdnWait: If I understand well, than my modifications will be performed on the background thread but they are safe due to I used a performBlock*. Even if the main context is used which is belonging to the main thread?Identity
@ingaham Not sure I get exactly what you are talking about, but any code you run from within a performBlock or performBlockAndWait should be fine, relative to the MOC that is the receiver of the performBlock* method. Now, note that the performBlockAndWait should rarely be used, and then only if you have a really good reason (i.e., it does not work otherwise) - from experience, that's extremely rare.Borg
Thanks! It's a little bit strange for me, that if I have a mainContext (a MOC created with NSMainQueueConcurrencyType to perform CD operations on the main thread) is able to perform CD operations even on a background thread. But in the case, when you call the [mainContext performBlockAndWait:^{...}] on a background thread, than it will.Identity
Remember, the multithread support for Core Data is QUEUE based... not THREAD based. You should only think of threads, in that it is safe to do any main-thread stuff while running in a MOC that is NSMainContextConcurrencyType and that should help you stay clean.Borg
This answer is simply wrong. Too bad, the answer has too many accepts.Functional
@Functional - Maybe you want to enlighten us as to what's wrong. It's from 2012, and it's possible things have changed since then, but the last time I checked (2015) it was still the same.Borg
@JodyHagins performBlockAndWait (and performBlock) will always execute in the private specified queue. This is important, since this guaranties concurrency constraints. In the example, performBlockAndWait just happens to execute on the main thread, since it was the main thread where the call-site submitted the block which will be called synchronously. It's just an optimisation that dispatch lib will execute the block on the call-site's thread (this is documented) which may happen when the block is synchronously dispatched. Note that, GCD can choose any thread to execute a block.Functional
@JodyHagins Just to be clear: a block submitted to a certain dispatch queue will always execute on that queue. GCD assigns a thread to this queue when a block will be executed. This thread may change for a certain queue - but not for a single block. It can be any thread which is assigned to a queue. There's just one exception: the main_queue always uses the main thread to execute blocks.Functional
M
3

Why not? Grand Central Dispatch's block concurrency paradigm (which I assume MOC uses internally) is designed so that only the runtime and operating system need to worry about threads, not the developer (because the OS can do it better than you can do to having more detailed information). Too many people assume that queues are the same as threads. They are not.

Queued blocks are not required to run on any given thread (the exception being blocks in the main queue must execute on the main thread). So, in fact, sometimes sync (i.e. performBlockAndWait) queued blocks will run on the main thread if the runtime feels it would be more efficient than creating a thread for it. Since you are waiting for the result anyway, it wouldn't change the way your program functioned if the main thread were to hang for the duration of the operation.

This last part I am not sure if I remember correctly, but in the WWDC 2011 videos about GCD, I believe that it was mentioned that the runtime will make an effort to run on the main thread, if possible, for sync operations because it is more efficient. In the end though, I suppose the answer to "why" can only be answered by the people who designed the system.

Mileage answered 6/8, 2012 at 16:52 Comment(2)
Core Data does not use dispatch_sync internally for performBlockAndWait. You'll notice from the documentation that performBlockAndWait is reentrant.Dreeda
I didn't say it did. I said it uses the same idea.Mileage
M
0

I don't think that the MOC is obligated to use a background thread; it's just obligated to ensure that your code will not run into concurrency issues with the MOC if you use performBlock: or performBlockAndWait:. Since performBlockAndWait: is supposed to block the current thread, it seems reasonable to run that block on that thread.

Mease answered 6/8, 2012 at 16:10 Comment(7)
But what if I have code in the block that is related to that context's background thread? Isn't it dangerous for it to run on the main thread? For example if I do [backgroundMOC save:nil];, and that executes on the main thread rather than the bg thread, isn't that problematic?Endearment
No, because the MOC is guaranteeing you that if you do anything to backgroundMOC inside the performBlock: or performBlockAndWait: blocks then you're safe from threading issues. (In other words, it's probably preventing any code from running on the background queue while your block is running.)Mease
So if I'm using performBlock:, should I never make any assertion statements about the thread inside the block? Or is that just for performBlockAndWait:?Endearment
performBlockAndWait, as the name says, waits until the block has completed execution, therefore nothing bad can happen. I think it is a performance optimization that the block is executed on the same thread. - And yes, you should not make assumptions on which thread the blocks are executed.Buote
I would say that making assertions about the thread you're running on in either performBlock: or performBlockAndWait: is worrying about implementation details of the MOC. Unfortunately, I don't see any way to do something like assert([myMoc isPerformingBlock]).Mease
So is it possible that performBlock: for the backgroundMOC could at all run on the main thread?Endearment
"So is it possible that performBlock: for the backgroundMOC could at all run on the main thread?" Yes, because there is no guarantee that it will not. You can guarantee that a block WILL run on the main queue by using a main queue context, but you can't guarantee that it will not by using a private queue context. Additionally, if the context's parent is a main queue context, work may be enqueued there as a result of this block.Dreeda
B
0

The performBlockAndWait: call only makes sure that you execute the code in such a way that you don't introduce concurrency (i.e. on 2 threads performBlockAndWait: will not run at the same time, they will block each other).

The long and the short of it is that you can't depend on which thread a MOC operation runs on, well basically ever. I've learned the hard way that if you use GCD or just straight up threads, you always have to create local MOCs for each operation and then merge them to the master MOC.

There is a great library (MagicalRecord) that makes that process very simple.

Beastings answered 6/8, 2012 at 16:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.