performBlock:和performBlockAndWait之间的行为差​​异:?

App*_*Dev 25 synchronization asynchronous core-data nsmanagedobjectcontext ios

我正在NSManagedObjectContext私有队列中创建一个处理来自文件和/或服务的数据更新:

NSManagedObjectContext *privateContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
AppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
privateContext.persistentStoreCoordinator = appDelegate.persistentStoreCoordinator;
Run Code Online (Sandbox Code Playgroud)

由于我使用的是私有队列,我不完全理解performBlock:performBlockAndWait:方法之间的区别......为了执行我的数据更新,我目前正在这样做:

[privateContext performBlock: ^{

        // Parse files and/or call services and parse
        // their responses

        // Save context
        [privateContext save:nil];

        dispatch_async(dispatch_get_main_queue(), ^{
            // Notify update to user
        });
    }];
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我的数据更新是同步和顺序进行的,所以我认为这是保存上下文的正确位置,对吧?如果我做错了什么,如果你告诉我,我会很感激.另一方面,这段代码是否相同?:

[privateContext performBlockAndWait: ^{

        // Parse files and/or call services and parse
        // their responses

        // Save context
        [privateContext save:nil];
    }];

// Notify update to user
Run Code Online (Sandbox Code Playgroud)

我想这是保存上下文的正确位置......这两种方法之间有什么区别(如果有的话,在这种情况下)?

如果不是执行同步服务调用或文件解析,我需要执行异步服务调用怎么办?如何管理这些数据更新?

提前致谢

Jod*_*ins 72

你是正确的,因为任何你想用MOC做的事都必须在其中一个performBlock或两个之内完成performBlockAndWait.请注意,保留/释放对于托管对象是线程安全的,因此您不必在其中一个块内部保留/释放托管对象上的引用计数.

它们都使用同步队列来处理消息,这意味着一次只能执行一个块.嗯,这几乎是真的.请参阅说明performBlockAndWait.无论如何,对MOC的访问将被序列化,使得一次只有一个线程访问MOC.

tl; dr不要担心差异,并且总是使用performBlock.

事实差异

有许多不同之处.我相信还有更多,但这些是我认为最重要的理解.

同步与异步

performBlock是异步的,因为它会立即返回,并且在将来的某个时间,在一些未公开的线程上执行该块.提供给MOC via的所有块performBlock将按添加顺序执行.

performBlockAndWait是同步的,因为调用线程将在返回之前等待块执行.块是在其他线程中运行,还是在调用线程中运行并不是那么重要,并且是一个不可信任的实现细节.

但是请注意,它可以实现为"嘿,其他一些线程,运行这个块.我会坐在这里什么都不做,直到你告诉我它完成了." 或者,它可以实现为"嘿,核心数据,给我一个阻止所有其他块运行的锁,这样我就可以在我自己的线程上运行这个块." 或者它可以以其他方式实现.再次,实现细节,可随时更改.

我会告诉你这个,但是我最后一次测试它,performBlockAndWait在调用线程上执行了块(意味着上一段中的第二个选项).这只是帮助您了解正在发生的事情的真正信息,不应以任何方式依赖.

重入

performBlock始终是异步的,因此不是可重入的.好吧,有些人可能认为它是可重入的,因为你可以在一个被调用的块中调用它performBlock.但是,如果执行此操作,则所有调用performBlock将立即返回,并且块将不会执行,直到至少当前正在执行的块完全完成其工作.

[moc performBlock:^{
    doSomething();
    [moc performBlock:^{
      doSomethingElse();
    }];
    doSomeMore();
}];
Run Code Online (Sandbox Code Playgroud)

这些函数将始终按以下顺序执行:

doSomething()
doSomeMore()
doSomethingElse()
Run Code Online (Sandbox Code Playgroud)

performBlockAndWait总是同步的.此外,它也是可重入的.多次通话不会死锁.因此,如果你performBlockAndWait在一个由另一个人运行的块内部时最终调用performBlockAndWait,那么就可以了.您将获得预期的行为,因为第二次调用(以及任何后续调用)不会导致死锁.此外,第二个将在它返回之前完全执行,正如您所期望的那样.

[moc performBlockAndWait:^{
    doSomething();
    [moc performBlockAndWait:^{
      doSomethingElse();
    }];
    doSomeMore();
}];
Run Code Online (Sandbox Code Playgroud)

这些函数将始终按以下顺序执行:

doSomething()
doSomethingElse()
doSomeMore()
Run Code Online (Sandbox Code Playgroud)

FIFO

FIFO代表"先入先出",这意味着块将按照它们放入内部队列的顺序执行.

performBlock始终尊重内部队列的FIFO结构.每个块都将插入到队列中,并且仅在以FIFO顺序删除时才会运行.

根据定义,performBlockAndWait打破FIFO排序,因为它跳转已排队的块队列.

提交的块performBlockAndWait不必等待队列中正在运行的其他块.有很多方法可以看到这一点.一个简单的就是这个.

[moc performBlock:^{
    doSomething();
    [moc performBlock:^{
      doSomethingElse();
    }];
    doSomeMore();
    [moc performBlockAndWait:^{
      doSomethingAfterDoSomethingElse();
    }];
    doTheLastThing();
}];
Run Code Online (Sandbox Code Playgroud)

这些函数将始终按以下顺序执行:

doSomething()
doSomeMore()
doSomethingAfterDoSomethingElse()
doTheLastThing()
doSomethingElse()
Run Code Online (Sandbox Code Playgroud)

在这个例子中很明显,这就是我使用它的原因.但是,请考虑一下,如果您的MOC正在从多个地方收到调用它的内容.可能有点混乱.

但要记住的是,这performBlockAndWait是先发制人的,可以跳过FIFO队列.

僵局

你永远不会遇到死锁performBlock.如果你在块中做了一些愚蠢的事情,那么你可能会死锁,但是调用performBlock永远不会死锁.您可以从任何地方调用它,它只是将块添加到队列中,并在将来的某个时间执行它.

您可以轻松地获得死锁调用performBlockAndWait,特别是如果您从外部实体可以在嵌套上下文中调用或不加区别地调用它的方法调用它.具体来说,如果父母呼叫performBlockAndWait孩子,几乎可以保证你的应用程序死锁.

用户事件

Core Data将"用户事件"视为调用之间的任何内容processPendingChanges.您可以阅读此方法重要性的详细信息,但"用户事件"中发生的情况会对通知,撤消管理,删除传播,更改合并等产生影响.

performBlock封装"用户事件",这意味着代码块在不同的调用之间自动执行processPendingChanges.

performBlockAndWait没有封装"用户事件".如果您希望将块视为不同的用户事件,则必须自己执行此操作.

自动发布池

performBlock 将块包装在自己的autoreleasepool中.

performBlockAdWait不提供独特的自动释放池.如果您需要,您必须自己提供.

个人意见

就个人而言,我不相信有很多好的理由可以使用performBlockAndWait.我敢肯定某人有一个无法以其他方式完成的用例,但我还没有看到它.如果有人知道该用例,请与我分享.

最接近的是调用performBlockAndWait父上下文(不要在NSMainConcurrencyTypeMOC 上执行此操作,因为它可能会锁定您的UI).例如,如果要确保在当前块返回之前数据库已完全保存到磁盘,并且其他块有机会运行.

因此,不久前,我决定将Core Data视为完全异步的API.结果,我有很多核心数据代码performBlockAndWait,在测试之外我没有一次调用.

这样生活好多了.当我认为"它必须有用或者它们不会提供它"时,我遇到的问题比我回来的要少得多.

现在,我根本不再需要performBlockAndWait.结果,也许它已经改变了一些,我只是错过了它因为它不再感兴趣...但我对此表示怀疑.