Core Data多线程环境下pendingChange引发的排序不对问题

简介:

这是一个起初看起来很神奇的问题,大意如下:

  • 有一个Table,展示多个消息会话,这些消息会话按最新消息时间排序;
  • 某种情况下,新收到一条消息,时间展示为最新,但这条消息没有排在最上方。

因为界面上展示的时间是最新的,所以刚开始遇到这个问题的第一反应是看看数据库里面的时间戳是不是正确的,查看后确认时间是最新的没错。

一时陷入了僵局,因为问题很难重现。

所以梳理了下逻辑:

  1. 收到新消息,在后台进行处理,执行save动作;
  2. Core Data保存后发出消息通知变更,主线程使用NSFetchedResultsController和UITableView绑定,收到消息后刷新界面;
  3. UI界面根据dataSource进行展现,而dataSource根据latestTime进行排序;

因为无法重现,所以先加上了日志输出信息,观察出了发生该现象的时候,主线程都收到两次刷新通知,正常情况下没有。

主线程为什么会发生两次刷新通知呢?

  • 主线程内存上发生了变动;
  • 其它线程对持久化层做了写动作,通知到主线程。

所以我就在想主线程在内存上发生了什么变动,找了很久但是没找到什么东西。后来同事一语道破天机,打印出changeValues:

<span style="font-size:14px">- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    NSLog(@"didChangeObject %@ from %@ to %@ with %d type, \n change value : %@\n", anObject, indexPath, newIndexPath, type, [anObject changedValues]);
}</span>

通过这样的日志信息可以发现主线程在内存中发生了什么变化。

为了验证问题是不是这样引发的,我在一个Demo上进行了模拟和验证(这个Demo是之前一篇博文使用的):

我通过在主线程修改Core Data对象的值(不一定要sortKey),但不保存:

<span style="font-size:14px">    Player *playerObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i];
    playerObject.name = [NSString stringWithFormat:@"name-%d", arc4random() % 10000];</span>

接着在其它线程修改sortKey,引发主线程进行刷新:

<span style="font-size:14px">- (void)changeSortKeyInOtherContext:(NSManagedObjectID *)objectId
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSManagedObjectContext *tmpContext = [[NSManagedObjectContext alloc] init];
        
        NSPersistentStoreCoordinator *sharedPersistentStoreCoordinator = self.cdViewController.persistentStoreCoordinator;
        [tmpContext setPersistentStoreCoordinator:sharedPersistentStoreCoordinator];
        
        Player *playerObject = (Player *)[tmpContext objectWithID:objectId];
        
        int age = arc4random() % 100;
        playerObject.age = @(age);
        
        int salary = arc4random() % 10000000;
        playerObject.salary = @(salary);
        
        NSError *error = NULL;
        if (tmpContext && [tmpContext hasChanges] && ![tmpContext save:&error]) {
            NSLog(@"Error %@, %@", error, [error localizedDescription]);
            abort();
        }
        
        [tmpContext release], tmpContext = nil;
    });
}</span>

这样就可以模拟出问题场景,进而得到验证。

—— Jason Lee @ Hangzhou


目录
相关文章
|
6月前
|
消息中间件 安全 Linux
线程同步与IPC:单进程多线程环境下的选择与权衡
线程同步与IPC:单进程多线程环境下的选择与权衡
160 0
|
6月前
|
缓存 安全 Java
为什么全局变量可能成为多线程环境中的安全隐患
为什么全局变量可能成为多线程环境中的安全隐患
|
5月前
|
存储 缓存 开发框架
多线程环境下的伪共享
多线程环境下的伪共享
|
5月前
|
分布式计算 并行计算 安全
在Python Web开发中,Python的全局解释器锁(Global Interpreter Lock,简称GIL)是一个核心概念,它直接影响了Python程序在多线程环境下的执行效率和性能表现
【6月更文挑战第30天】Python的GIL是CPython中的全局锁,限制了多线程并行执行,尤其是在多核CPU上。GIL确保同一时间仅有一个线程执行Python字节码,导致CPU密集型任务时多线程无法充分利用多核,反而可能因上下文切换降低性能。然而,I/O密集型任务仍能受益于线程交替执行。为利用多核,开发者常选择多进程、异步IO或使用不受GIL限制的Python实现。在Web开发中,理解GIL对于优化并发性能至关重要。
61 0
|
6月前
|
消息中间件 SQL Kubernetes
实时计算 Flink版产品使用合集之多线程环境中,遇到 env.addSource 添加数据源后没有执行到 env.execut,是为什么
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
3月前
|
缓存 Java 容器
多线程环境中的虚假共享是什么?
【8月更文挑战第21天】
33 0
|
3月前
|
Cloud Native Java 调度
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决
|
4月前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
78 1
|
5月前
|
Java
Java中的`synchronized`关键字是一个用于并发控制的关键字,它提供了一种简单的加锁机制来确保多线程环境下的数据一致性。
【6月更文挑战第24天】Java的`synchronized`关键字确保多线程数据一致性,通过锁定代码块或方法防止并发冲突。同步方法整个方法体为临界区,同步代码块则锁定特定对象。示例展示了如何在`Counter`类中使用`synchronized`保证原子操作和可见性,同时指出过度使用可能影响性能。
43 4

热门文章

最新文章