注意:
This post不适用,因为我实际上使用的是CoreData。
在this post中,最后一个答案建议在添加新对象之前先获取新后台线程中的所有项目,但这是在我的代码中完成的。
This post建议在保存项目上下文之前先对其进行取消操作,但这也在我的代码中完成。
我的应用程序使用CoreData存储名为shoppingItems
的对象。我编写了一个类CoreDataManager
,该类初始化CoreData,并且实质上具有一个函数来覆盖当前存储的项,以及一个函数来获取所有项。这两个函数都在后台运行,即在单独的线程上运行。
这是我的代码(不相关的部分被省略了)。
我在主线程上设置了核心数据:
private lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: modelName)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
})
return container
}()
这是写功能:
func overwriteShoppingItems(_ shoppingItems: Set<ShoppingItem>, completion: @escaping (Error?) -> Void) {
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
let viewContext = self.persistentContainer.viewContext
backgroundContext.parent = viewContext
backgroundContext.performAndWait {
// Delete currently stored shopping items
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: CDEntityShoppingItem)
do {
let result = try backgroundContext.fetch(fetchRequest)
let resultData = result as! [NSManagedObject]
for object in resultData {
backgroundContext.delete(object)
}
if !shoppingItems.isEmpty {
// Save shopping items in managed context
let cdShoppingItemEntity = NSEntityDescription.entity(forEntityName: CDEntityShoppingItem, in: backgroundContext)!
for nextShoppingItem in shoppingItems {
let nextCdShoppingItem = CDShoppingItem(entity: cdShoppingItemEntity, insertInto: backgroundContext)
nextCdShoppingItem.name = nextShoppingItem.name
}
}
let saveError = self.saveManagedContext(managedContext: backgroundContext)
completion(saveError)
} catch let error as NSError {
completion(error)
}
}
}
func saveManagedContext(managedContext: NSManagedObjectContext) -> Error? {
if !managedContext.hasChanges { return nil }
do {
try managedContext.save()
return nil
} catch let error as NSError {
return error
}
}
这是获取功能:
func fetchShoppingItems(completion: @escaping (Set<ShoppingItem>?, Error?) -> Void) {
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
let viewContext = self.persistentContainer.viewContext
backgroundContext.parent = viewContext
backgroundContext.performAndWait {
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
do {
let cdShoppingItems: [CDShoppingItem] = try backgroundContext.fetch(fetchRequest)
guard !cdShoppingItems.isEmpty else {
completion([], nil)
return
}
for nextCdShoppingItem in cdShoppingItems {
}
completion(shoppingItems, nil)
} catch let error as NSError {
completion(nil, error)
}
}
}
在正常操作中,该代码似乎可以正常工作。
问题:
我还写了一个单元测试,试图引发多线程问题。此测试使用并发调度队列:
let concurrentReadWriteQueue = DispatchQueue(label: „xxx.test_coreDataMultithreading", attributes: .concurrent)
计时器定义测试时间。
在测试方案中,我设置了参数-com.apple.CoreData.Logging.stderr 1
和-com.apple.CoreData.ConcurrencyDebug 1
。
在测试期间,overwriteShoppingItems
和fetchShoppingItems
被重复插入队列,并同时执行。
此单元测试将在行
let itemName = nextCdShoppingItem.name!
因为nextCdShoppingItem.name
是nil
,所以永远不会发生,因为我从不存储nil
。
在崩溃之前,立即记录以下内容:
CoreData: error: API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600000e6c980, store PSC = 0x0)
如果我只提取或仅写入,则不记录CoreData警告。因此,这似乎绝对是一个多线程问题。
但是,CoreData.ConcurrencyDebug
无法检测到它。
似乎在一个线程上进行获取操作期间,另一线程删除了当前获取的项目,因此其属性被读为nil
。
但这不会发生,因为获取和保存是通过backgroundContext.performAndWait
完成的,即串行进行的。
并且堆栈跟踪显示只有一个线程访问CoreData:Thread 3 Queue : NSManagedObjectContext 0x600003c8c000 (serial)
我的问题:
编辑:
也许这有助于确定问题:当我在backgroundContext.delete(object)
中注释掉overwriteShoppingItems
时,不再记录该错误,并且没有任何内容被提取为nil
。
答案 0 :(得分:1)
如果您没有在项目中使用核心数据,请检查您的资产文件是否正确。
我也收到这样的错误,然后我从我的项目中发现了这个错误。
答案 1 :(得分:0)
问题似乎已经解决。
显然发生了这种情况,因为函数overwriteShoppingItems
和fetchShoppingItems
都使用自己的队列与let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
设置了单独的后台上下文,因此获取和保存操作没有被单个队列序列化。
我现在通过以下方式修改了代码:
我现在有一个财产
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
初始化为的
self.backgroundContext.persistentStoreCoordinator = self.persistentContainer.persistentStoreCoordinator
self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
并使用
进行获取和保存backgroundContext.perform {…}
现在,不再记录CoreData错误,并且没有任何项目提取为nil。
答案 2 :(得分:0)
就我而言,我创建了一个新的managedObjectContext
,但尚未分配持久存储。最终,我在商店为零的情况下致电-[psc metadataForPersistentStore:store]
。这导致日志消息被写入。
(旁注:您始终可以通过在fprintf上设置断点来捕获这些输出)