NSManagedObjectContext performBlockAndWait:不执行后台线程吗?

时间:2022-05-02 02:29:25

I have an NSManagedObjectContext declared like so:

我有一个NSManagedObjectContext声明为:

- (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?

为什么调用performBlockAndWait在主线程而不是后台线程上执行任务?

4 个解决方案

#1


93  

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

抛出另一个答案,尝试解释为什么performBlockAndWait总是在调用线程中运行。

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

performBlock完全是异步的。它总是将块排队到接收MOC的队列中,然后立即返回。因此,

[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).

将在moc队列上放置两个块。它们总是异步执行。一些未知的线程将从队列中取出块并执行它们。此外,这些块被包装在它们自己的autorelease池中,而且它们还将表示一个完整的核心数据用户事件(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).

performBlockAndWait不使用内部队列。它是在调用线程的上下文中执行的同步操作。当然,它将等待队列上的当前操作被执行,然后该块将在调用线程中执行。这是有记录的(并且在几个WWDC的介绍中重申)。

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

此外,performBockAndWait是可重入的,因此嵌套调用都在该调用线程中发生。

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.

核心数据工程师非常清楚,运行基于队列的MOC操作的实际线程并不重要。关键是使用performBlock* API的同步。

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"

因此,考虑“performBlock”作为“这个块被放置在队列上,在一些未确定的时间内执行,在一些未确定的线程中”。一旦函数被加入队列,它就会返回给调用者"

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)."

performBlockAndWait是“这个块将在某个未确定的时间在相同的线程中执行。”该函数将在此代码完全执行后返回(在与MOC关联的当前队列耗尽之后返回)。

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

您确定“performBlockAndWait不使用内部队列”吗?我认为它是我的。唯一的区别是performBlockAndWait将等待到块的完成。你说的线程是什么意思?在我的理解中,[moc performBlockAndWait]和[moc performBloc]都运行在它的私有队列(后台或主队列)上。这里的重要概念是moc拥有队列,而不是相反。如果我说错了,请纠正我。——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."

不幸的是,我把答案说得和我一样,因为就其本身而言,它是不正确的。然而,在原始问题的背景下,它是正确的。具体地说,当调用performBlockAndWait在一个私有队列上时,块将在调用函数的线程上执行——它不会被放在队列上并在“私有线程”上执行。

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.

现在,在我深入讨论细节之前,我想强调,依赖于库的内部工作是非常危险的。您真正应该关心的是,除了与主线程绑定的任何东西之外,您永远不可能期望一个特定的线程执行一个块。因此,不建议在主线程上执行performblock并等待不执行,因为它将在调用它的线程上执行。

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.

performBlockAndWait使用GCD,但它也有自己的层(例如,防止死锁)。如果查看GCD代码(它是开源的),您可以看到同步调用是如何工作的——通常它们与队列同步,并在调用函数的线程上调用块——除非队列是主队列或全局队列。此外,在WWDC的谈话中,核心数据工程师强调performBlockAndWait将在调用线程中运行。

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.

所以,当我说它不使用内部队列时,并不意味着它根本不使用数据结构。它必须将调用与队列中已经存在的块以及在其他线程和其他异步调用中提交的块同步。但是,当调用performBlockAndWait时,它不会将块放到队列中……相反,它同步访问并在调用函数的线程上运行提交的块。

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.

所以这不是一个很好的论坛,因为它比这个复杂一点,尤其是w.r.。它不是主队列,也不是GCD全局队列——但是后者对核心数据并不重要。

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.

要点是,当您调用任何performBlock*或GCD函数时,不应该期望它在任何特定的线程上运行(除了与主线程绑定的东西),因为队列不是线程,只有主队列会在特定的线程上运行块。

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).

当调用core data performBlockAndWait时,块将在调用线程中执行(但将与提交给队列的所有内容进行适当的同步)。

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...

此外,您还可以看到它的隐含意义,performBlockAndWait提供重入支持的方式打破了块的FIFO顺序。作为一个例子……

[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.

注意,严格遵守FIFO保证队列意味着嵌套的performBlockAndWait(“3”)将在异步块(“2”)之后运行,因为异步块是在提交之后提交的。然而,事实并非如此,因为这是不可能的。出于同样的原因,嵌套的dispatch_sync调用会导致死锁。如果使用的是同步版本,请注意这一点。

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.

一般来说,尽可能避免同步版本,因为dispatch_sync可能会导致死锁,任何可重入版本(比如performBlockAndWait)都必须做出一些“糟糕”的决定来支持它……比如同步版本“跳转”队列。

#2


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.

为什么不呢?Grand Central Dispatch的块并发范例(我假设MOC在内部使用)是设计的,因此只有运行时和操作系统需要担心线程,而不是开发人员(因为操作系统可以做得比拥有更详细的信息更好)。太多的人认为队列与线程相同。他们不是。

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.

队列块不需要在任何给定的线程上运行(主队列中的块必须在主线程上执行)。因此,实际上,如果运行时认为同步(例如performBlockAndWait)要比为主线程创建一个线程更有效,那么它就会在主线程上运行。由于您正在等待结果,所以如果主线程在操作期间挂起,它不会改变程序的运行方式。

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.

最后一部分我不确定我是否记得正确,但是在关于GCD的WWDC 2011视频中,我相信有人提到运行时将努力在主线程上运行,如果可能的话,为了同步操作,因为它更高效。最后,我想“为什么”的答案只能由设计这个系统的人来回答。

#3


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.

我不认为MOC有义务使用后台线程;如果您使用performBlock:或performBlockAndWait:,则有义务确保您的代码不会与MOC发生并发问题。由于performBlockAndWait:应该阻塞当前线程,所以在该线程上运行该块似乎是合理的。

#4


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).

performBlockAndWait: call只确保以不引入并发的方式执行代码(例如,在两个线程performBlockAndWait:不会同时运行,它们会互相阻塞)。

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.

它的长和短是你不能依赖于MOC操作运行在哪个线程上,基本上从来没有。我了解到,如果您使用GCD或直接使用线程,您必须为每个操作创建本地MOC,然后将它们合并到主MOC。

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

有一个很棒的库(MagicalRecord)使这个过程非常简单。

#1


93  

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

抛出另一个答案,尝试解释为什么performBlockAndWait总是在调用线程中运行。

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

performBlock完全是异步的。它总是将块排队到接收MOC的队列中,然后立即返回。因此,

[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).

将在moc队列上放置两个块。它们总是异步执行。一些未知的线程将从队列中取出块并执行它们。此外,这些块被包装在它们自己的autorelease池中,而且它们还将表示一个完整的核心数据用户事件(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).

performBlockAndWait不使用内部队列。它是在调用线程的上下文中执行的同步操作。当然,它将等待队列上的当前操作被执行,然后该块将在调用线程中执行。这是有记录的(并且在几个WWDC的介绍中重申)。

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

此外,performBockAndWait是可重入的,因此嵌套调用都在该调用线程中发生。

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.

核心数据工程师非常清楚,运行基于队列的MOC操作的实际线程并不重要。关键是使用performBlock* API的同步。

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"

因此,考虑“performBlock”作为“这个块被放置在队列上,在一些未确定的时间内执行,在一些未确定的线程中”。一旦函数被加入队列,它就会返回给调用者"

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)."

performBlockAndWait是“这个块将在某个未确定的时间在相同的线程中执行。”该函数将在此代码完全执行后返回(在与MOC关联的当前队列耗尽之后返回)。

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

您确定“performBlockAndWait不使用内部队列”吗?我认为它是我的。唯一的区别是performBlockAndWait将等待到块的完成。你说的线程是什么意思?在我的理解中,[moc performBlockAndWait]和[moc performBloc]都运行在它的私有队列(后台或主队列)上。这里的重要概念是moc拥有队列,而不是相反。如果我说错了,请纠正我。——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."

不幸的是,我把答案说得和我一样,因为就其本身而言,它是不正确的。然而,在原始问题的背景下,它是正确的。具体地说,当调用performBlockAndWait在一个私有队列上时,块将在调用函数的线程上执行——它不会被放在队列上并在“私有线程”上执行。

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.

现在,在我深入讨论细节之前,我想强调,依赖于库的内部工作是非常危险的。您真正应该关心的是,除了与主线程绑定的任何东西之外,您永远不可能期望一个特定的线程执行一个块。因此,不建议在主线程上执行performblock并等待不执行,因为它将在调用它的线程上执行。

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.

performBlockAndWait使用GCD,但它也有自己的层(例如,防止死锁)。如果查看GCD代码(它是开源的),您可以看到同步调用是如何工作的——通常它们与队列同步,并在调用函数的线程上调用块——除非队列是主队列或全局队列。此外,在WWDC的谈话中,核心数据工程师强调performBlockAndWait将在调用线程中运行。

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.

所以,当我说它不使用内部队列时,并不意味着它根本不使用数据结构。它必须将调用与队列中已经存在的块以及在其他线程和其他异步调用中提交的块同步。但是,当调用performBlockAndWait时,它不会将块放到队列中……相反,它同步访问并在调用函数的线程上运行提交的块。

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.

所以这不是一个很好的论坛,因为它比这个复杂一点,尤其是w.r.。它不是主队列,也不是GCD全局队列——但是后者对核心数据并不重要。

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.

要点是,当您调用任何performBlock*或GCD函数时,不应该期望它在任何特定的线程上运行(除了与主线程绑定的东西),因为队列不是线程,只有主队列会在特定的线程上运行块。

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).

当调用core data performBlockAndWait时,块将在调用线程中执行(但将与提交给队列的所有内容进行适当的同步)。

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...

此外,您还可以看到它的隐含意义,performBlockAndWait提供重入支持的方式打破了块的FIFO顺序。作为一个例子……

[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.

注意,严格遵守FIFO保证队列意味着嵌套的performBlockAndWait(“3”)将在异步块(“2”)之后运行,因为异步块是在提交之后提交的。然而,事实并非如此,因为这是不可能的。出于同样的原因,嵌套的dispatch_sync调用会导致死锁。如果使用的是同步版本,请注意这一点。

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.

一般来说,尽可能避免同步版本,因为dispatch_sync可能会导致死锁,任何可重入版本(比如performBlockAndWait)都必须做出一些“糟糕”的决定来支持它……比如同步版本“跳转”队列。

#2


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.

为什么不呢?Grand Central Dispatch的块并发范例(我假设MOC在内部使用)是设计的,因此只有运行时和操作系统需要担心线程,而不是开发人员(因为操作系统可以做得比拥有更详细的信息更好)。太多的人认为队列与线程相同。他们不是。

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.

队列块不需要在任何给定的线程上运行(主队列中的块必须在主线程上执行)。因此,实际上,如果运行时认为同步(例如performBlockAndWait)要比为主线程创建一个线程更有效,那么它就会在主线程上运行。由于您正在等待结果,所以如果主线程在操作期间挂起,它不会改变程序的运行方式。

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.

最后一部分我不确定我是否记得正确,但是在关于GCD的WWDC 2011视频中,我相信有人提到运行时将努力在主线程上运行,如果可能的话,为了同步操作,因为它更高效。最后,我想“为什么”的答案只能由设计这个系统的人来回答。

#3


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.

我不认为MOC有义务使用后台线程;如果您使用performBlock:或performBlockAndWait:,则有义务确保您的代码不会与MOC发生并发问题。由于performBlockAndWait:应该阻塞当前线程,所以在该线程上运行该块似乎是合理的。

#4


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).

performBlockAndWait: call只确保以不引入并发的方式执行代码(例如,在两个线程performBlockAndWait:不会同时运行,它们会互相阻塞)。

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.

它的长和短是你不能依赖于MOC操作运行在哪个线程上,基本上从来没有。我了解到,如果您使用GCD或直接使用线程,您必须为每个操作创建本地MOC,然后将它们合并到主MOC。

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

有一个很棒的库(MagicalRecord)使这个过程非常简单。