Update NSFetchedResultsController using performBackgroundTask - ios

I have an NSFetchedResultsController and I am trying to update my data on a background context. For example, here I am trying to delete an object:
persistentContainer.performBackgroundTask { context in
let object = context.object(with: restaurant.objectID)
context.delete(object)
try? context.save()
}
There are 2 things I don't understand:
I would have expected this to modify, but not save the parent context. However, the parent context is definitely being saved (as verified by manually opening the SQLite file).
I would have expected the NSFetchedResultsController to update when the background content saves back up to its parent, but this is not happening. Do I need to manually trigger something on the main thread?
Obviously there is something I am not getting. Can anybody explain this?
I know that I have implemented the fetched results controller delegate methods correctly, because if I change my code to directly update the viewContext, everything works as expected.

Explanation
NSPersistentContainer's instance methods performBackgroundTask(_:) and newBackgroundContext() are poorly documented.
No matter which method you call, in either case the (returned) temporary NSManagedObjectContext is set up with privateQueueConcurrencyType and is associated with the NSPersistentStoreCoordinator directly and therefore has no parent.
See documentation:
Invoking this method causes the persistent container to create and
return a new NSManagedObjectContext with the concurrencyType set to
privateQueueConcurrencyType. This new context will be associated with
the NSPersistentStoreCoordinator directly and is set to consume
NSManagedObjectContextDidSave broadcasts automatically.
... or confirm it yourself:
persistentContainer.performBackgroundTask { (context) in
print(context.parent) // nil
print(context.persistentStoreCoordinator) // Optional(<NSPersistentStoreCoordinator: 0x...>)
}
let context = persistentContainer.newBackgroundContext()
print(context.parent) // nil
print(context.persistentStoreCoordinator) // Optional(<NSPersistentStoreCoordinator: 0x...>)
Due to the lack of a parent, changes won't get committed to a parent context like e.g. the viewContext and with the viewContext untouched, a connected NSFetchedResultsController won’t recognize any changes and therefore doesn’t update or call its delegate's methods. Instead changes will be pushed directly to the persistent store coordinator and after that saved to the persistent store.
I hope, that I was able to help you and if you need further assistance, I can add, how to get the desired behavior, as described by you, to my answer. (Solution added below)
Solution
You achieve the behavior, as described by you, by using two NSManagedObjectContexts with a parent-child relationship:
// Create new context for asynchronous execution with privateQueueConcurrencyType
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
// Add your viewContext as parent, therefore changes are pushed to the viewContext, instead of the persistent store coordinator
let viewContext = persistentContainer.viewContext
backgroundContext.parent = viewContext
backgroundContext.perform {
// Do your work...
let object = backgroundContext.object(with: restaurant.objectID)
backgroundContext.delete(object)
// Propagate changes to the viewContext -> fetched results controller will be notified as a consequence
try? backgroundContext.save()
viewContext.performAndWait {
// Save viewContext on the main queue in order to store changes persistently
try? viewContext.save()
}
}
However, you can also stick with performBackgroundTask(_:) or use newBackgroundContext(). But as said before, in this case changes are saved to the persistent store directly and the viewContext isn't updated by default. In order to propagate changes down to the viewContext, which causes NSFetchedResultsController to be notified, you have to set viewContext.automaticallyMergesChangesFromParent to true:
// Set automaticallyMergesChangesFromParent to true
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
persistentContainer.performBackgroundTask { context in
// Do your work...
let object = context.object(with: restaurant.objectID)
context.delete(object)
// Save changes to persistent store, update viewContext and notify fetched results controller
try? context.save()
}
Please note that extensive changes such as adding 10.000 objects at once will likely drive your NSFetchedResultsController mad and therefore block the main queue.

The view context will not update unless you have set it to automatically merge changes from the parent. The viewContext is already set as child of any backgroundContext that you receive from the NSPersistentContainer.
Try adding just this one line:
persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
Now, the viewContext WILL update after the backgroundContext has been saved and this WILL trigger the NSFetchedResultsController to update.

This works me perfectly in my project.
In function updateEnglishNewsListener(:), here parameter data is in anyobject and i further convert it into json formate for saving purpose.
Core Data uses thread (or serialized queue) confinement to protect managed objects and managed object contexts (see Core Data Programming Guide). A consequence of this is that a context assumes the default owner is the thread or queue that allocated it—this is determined by the thread that calls its init method. You should not, therefore, initialize a context on one thread then pass it to a different thread.
There are three type
1. ConfinementConcurrencyType
2. PrivateQueueConcurrencyType
3. MainQueueConcurrencyType
The MainQueueConcurrencyType creates a context associated with the main queue which is perfect for use with NSFetchedResultsController.
In updateEnglishNewsListener(:) function, params data is your input. (data->Data you wants to update.)
private func updateEnglishNewsListener(data: [AnyObject] ){
//Here is your data
let privateAsyncMOC_En = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
// The context is associated with the main queue, and as such is tied into the application’s event loop, but it is otherwise similar to a private queue-based context. You use this queue type for contexts linked to controllers and UI objects that are required to be used only on the main thread.
privateAsyncMOC_En.parent = managedObjectContext
privateAsyncMOC_En.perform{
// The perform(_:) method returns immediately and the context executes the block methods on its own thread. Here it use background thread.
let convetedJSonData = self.convertAnyobjectToJSON(anyObject: data as AnyObject)
for (_ ,object) in convetedJSonData{
self.checkIFNewsIdForEnglishAlreadyExists(newsId: object["news_id"].intValue, completion: { (count) in
if count != 0{
self.updateDataBaseOfEnglishNews(json: object, newsId: object["news_id"].intValue)
}
})
}
do {
if privateAsyncMOC_En.hasChanges{
try privateAsyncMOC_En.save()
}
if managedObjectContext.hasChanges{
try managedObjectContext.save()
}
}catch {
print(error)
}
}
}
Checking data is already exist in coredata or not for avoiding the redundance data. Coredata have not primary key concept so we sequently check data already exist in coredata or not. Data is updated if and only if updating data is already exist in coredata. Here checkIFNewsIdForEnglishAlreadyExists(:) function return 0 or value . If it returns 0 then data is not saved in database else saved. I am using completion handle for knowing the new data or old data.
private func checkIFNewsIdForEnglishAlreadyExists(newsId:Int,completion:(_ count:Int)->()){
let fetchReq:NSFetchRequest<TestEntity> = TestEntity.fetchRequest()
fetchReq.predicate = NSPredicate(format: "news_id = %d",newsId)
fetchReq.fetchLimit = 1 // this gives one data at a time for checking coming data to saved data
do {
let count = try managedObjectContext.count(for: fetchReq)
completion(count)
}catch{
let error = error as NSError
print("\(error)")
completion(0)
}
}
Replacing the old data to new one according to requirement.
private func updateDataBaseOfEnglishNews(json: JSON, newsId : Int){
do {
let fetchRequest:NSFetchRequest<TestEntity> = TestEntity.fetchRequest()
fetchRequest.predicate = NSPredicate(format: "news_id = %d",newsId)
let fetchResults = try managedObjectContext.fetch(fetchRequest as! NSFetchRequest<NSFetchRequestResult>) as? [TestEntity]
if let fetchResults = fetchResults {
if fetchResults.count != 0{
let newManagedObject = fetchResults[0]
newManagedObject.setValue(json["category_name"].stringValue, forKey: "category_name")
newManagedObject.setValue(json["description"].stringValue, forKey: "description1")
do {
if ((newManagedObject.managedObjectContext?.hasChanges) != nil){
try newManagedObject.managedObjectContext?.save()
}
} catch {
let saveError = error as NSError
print(saveError)
}
}
}
} catch {
let saveError = error as NSError
print(saveError)
}
}
Convertion anyobject to JSON for saving purpose in coredata
func convertAnyobjectToJSON(anyObject: AnyObject) -> JSON{
let jsonData = try! JSONSerialization.data(withJSONObject: anyObject, options: JSONSerialization.WritingOptions.prettyPrinted)
let jsonString = NSString(data: jsonData, encoding: String.Encoding.utf8.rawValue)! as String
if let dataFromString = jsonString.data(using: String.Encoding.utf8, allowLossyConversion: false) {
let json = JSON(data: dataFromString)
return json
}
return nil
}
Hope it will help you. If any confusion then please ask.

Related

CoreData error: API Misuse: Attempt to serialize store access on non-owning coordinator

Note:
This post does not apply since I actually use CoreData.
In this post the last answer suggests to fetch all items in the new background thread before adding new objects, but this is done in my code.
This post suggests to unfault an item before its context is saved, but this is also done in my code.
My app uses CoreData to store objects called shoppingItems. I wrote a class CoreDataManager that initialises CoreData and has essentially one function to overwrite the currently stored items, and one function to fetch all items. Both functions operate in the background, i.e. on a separate thread.
Here is my code (irrelevant parts are left out).
I setup core data on the main thread:
private lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: modelName)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
})
return container
}()
This is the write function:
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
}
}
And this is the fetch function:
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)
}
}
}
In normal operation, the code seems to work.
The problem:
I also wrote a unit test that tries to provoke multi-threading problems. This test uses a concurrent dispatch queue:
let concurrentReadWriteQueue = DispatchQueue(label: „xxx.test_coreDataMultithreading", attributes: .concurrent)
A timer defines the test time.
In the scheme for tests, I have set the arguments -com.apple.CoreData.Logging.stderr 1 and -com.apple.CoreData.ConcurrencyDebug 1.
During the test time, overwriteShoppingItems and fetchShoppingItems are inserted repeatedly in the queue, and executed concurrently.
This unit test executes a few reads and writes, before it stops at the line
let itemName = nextCdShoppingItem.name!
because nextCdShoppingItem.name is nil, which should never happen, because I never store nil.
Immediately before the crash, the following is logged:
CoreData: error: API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600000e6c980, store PSC = 0x0)
If I do only fetches, or only writes, the CoreData warning is not logged. Thus it seems definitely to be a multi-threading issue.
However, CoreData.ConcurrencyDebug does not detect it.
It looks as if during a fetch operation on one thread, the other thread deletes the currently fetched item, so that its properties are read back as nil.
But this should not happen because fetches and saves are done with backgroundContext.performAndWait, i.e. serially.
And the stack trace shows that only a single thread accesses CoreData: Thread 3 Queue : NSManagedObjectContext 0x600003c8c000 (serial)
My questions:
What is my misuse of the CoreData API, and how to do it right?
Why is an item sometimes read back as nil?
EDIT:
Maybe this helps to identify the problem: When I comment out backgroundContext.delete(object) in overwriteShoppingItems, the error is no longer logged, and no item is fetched as nil.
Okay so i got same error but only while "archiving" the project. Took me several hours to find the issue, since I was getting the error only on uploading, in the assets catalog, I had some duplicate images, by removing them the error is gone.
Anyone else getting this error AND your Coredata setup is fine, check your assets folder.
If you are not using core data in your project then check your assets files are proper or not.
I am also getting error like this then i have findout this error from my project.
Problem seems to be solved.
It happened apparently, because the functions overwriteShoppingItems and fetchShoppingItems both setup a separate background context with let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType) with their own queue so that fetches and saves were not serialized by a single queue.
I modified my code now in the following way:
I have now a property
let backgroundContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
that is initialized as
self.backgroundContext.persistentStoreCoordinator = self.persistentContainer.persistentStoreCoordinator
self.persistentContainer.viewContext.automaticallyMergesChangesFromParent = true
and fetches and saves are done using
backgroundContext.perform {…}
Now the CoreData error is no longer logged, and no item is fetched as nil.
In my case I created an new managedObjectContext without (yet) assigning a persistent store. Eventually I called -[psc metadataForPersistentStore:store] with the store being nil. This caused the log message to be written.
(side-note: you can always catch these outputs by setting a breakpoint on fprintf)
If you are seeing this during tear-down of a Core Data stack, check for un-removed observers or memory leaks.
In my case, I had managed objects which were each observing themselves – one of their relationships, using Swift's Key-Value Observing (NSKeyValueObservation objects). (Example use case: When the last a department's employees are deleted, delete the department.) The console messages appeared while the containing Core Data document was being closed. By breaking on fprintf I could see that the message was logged when a notification was being sent to an observer, and the number of messages was always equal to the number of non-faulted such objects in the managed object context (5 departments --> 5 console messages, etc., etc.). The problem was that I was not removing these observations, and the solution was of course to implement func willTurnIntoFault() in the observing objects, and in this func remove the observation.
In another situation, these messages stopped after I fixed a memory leak, but I did not study that in detail.
The full error messages were: error: API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600002c5cc00, store PSC = 0x0)
CoreData: error: API Misuse: Attempt to serialize store access on non-owning coordinator (PSC = 0x600002c5cc00, store PSC = 0x0) and they occurred during the call to NSPersistentStoreCoordinator.remove(_:). So, this error is telling me that the store's PSC was nil, but I checked this with the debugger and found that the store's PSC was not nil before the call. I filed a bug to Apple on this (FB11669843) asking if they could improve the error message.

iOS - Core Data - Creating an empty NSManagedObjectContext

My Core Data stack is as follows:
Private Queue Managed Object Context (Parent)
Main Queue Managed Object Context (Child)
My issue is that I want to create a Search functionality using ONLY data downloaded from the server.
The ideal solution was to create a new-empty NSManagedObjectContext(to be used a temp write/read context) without the need to have a Persistent Store or a Parent but Xcode will crash.
Any alternatives?
PS: One other solution would be to have a tempAttribute flag (bool attribute) that would be set to true only in the current context (which has a parent the Private Queue Managed Object Context but I want to avoid it.
Your managed object context does need a NSPersistentStore, but there is one type of NSPersistentStore, NSInMemoryStoreType, which in fact does not persist data to the disk. I think that is what you are looking for. Here is some Swift code for creating a managed object context with such an in-memory persistent store:
// Create Core Data Stack
guard let dataModel = NSManagedObjectModel.mergedModel(from: [Bundle.main]) else {return}
let psc = NSPersistentStoreCoordinator(managedObjectModel: dataModel)
do {
try psc.addPersistentStore(ofType: NSInMemoryStoreType,
configurationName: nil,
at: nil,
options: nil);
} catch {
print("Error creating store: \(error)")
}
let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
moc.persistentStoreCoordinator = psc
A good follow-up question is: Why does Apple call it NSPersistentStore if not all of its types are persistent? Well, maybe they added the in-memory store store type at the last minute before macOS 10.4 was frozen for release and did not have time to change the name :)
You should also read through the differences among store types

Saving data in child context with privateQueueConcurrencyType

Once I download data from server, I am trying to save data in Core Data. As per Apple documentation I am using parent-child context to perform save operation, as follow:
fileprivate func saveDataInLocalDataBase(_ mList: [Info]?) {
if mList != nil {
let mainContext = self.managedObjectContext
let privateContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
privateContext.parent = mainContext
privateContext.perform {
for mObj in mList! {
let entity = NSEntityDescription.entity(forEntityName: entityName, in:privateContext)
let managedObj = NSManagedObject(entity: entity!, insertInto:privateContext) as! NSManagedObject
mObj.populateFieldsForManagedObject(managedObj)
}
do {
try privateContext.save()
mainContext?.performAndWait {
do {
try mainContext?.save()
} catch {
fatalError("Failure to save context: \(error)")
}
}
} catch {
fatalError("Failure to save context: \(error)")
}
}
}
}
However, sometimes when I try to fetch data from core data it returns 0 objects. That means my data is not saving in Core Data. Can anyone know what is the problem, even though my service returns proper data.
Note: This issue occurs randomly.
Is it possible that when app tries to save data in background and user minimizes the app, so private context it unable to save data when app is in background?
Thanks in advance.
The answer to your question is no private has nothing to do with it. The data should still be saved in the background. You need to figure out the situation in which it is happening. Maybe the service stops running in the background as soon as you minimize the app.

Realm - database isn't up to date after a delete

I've got some objects in my Realm database. I need to delete an object from it.
When I delete my object from my Realm() instance, the objects are well deleted.
But after performing the delete request, I need to retrieve all objects from the database, but here surprise, the object is still here.
I think I have a thread problem, or something like that.
I don't know where to investigate.
My simplified code :
My delete method :
func myDeleteFunc(completion : ()->Void){
let realm = try! Realm()
// ...
try! realm.write {
realm.delete(myObject)
}
completion()
}
// Here the object have been deleted from the realm instance, no problem
This method is called from a viewController where I execute the completion block.
This completion block contains the request that retrieve all objects from my Realm database :
The viewController that executes the method and the completion block :
myDeleteFunc(completion: {
DispatchQueue.main.async {
let realm = try! Realm()
let objects = Array(realm.objects(MyObject.self).sorted(byProperty: "aProperty"))
// Here objects still contains the object that I have already deleted
// ...
}
}
I think my 2 realm instances differs, or have problems between threads because I have a DispatchQueue.main.async.
Any ideas ?
EDIT :
I noticed that when I check with breakpoints, sometimes it works.
So maybe that the delete request have not been committed yet, and that I retrieve the objects before the end of the delete request ?
Make sure you put the async block in an autorelease pool:
myDeleteFunc(completion: {
DispatchQueue.main.async {
let realm = try! Realm()
let objects = Array(realm.objects(MyObject.self).sorted(byProperty: "aProperty"))
// Here objects still contains the object that I have already deleted
// ...
}
}
Should be
myDeleteFunc(completion: {
DispatchQueue.main.async {
autoreleasepool {
let realm = try! Realm()
let objects = Array(realm.objects(MyObject.self).sorted(byProperty: "aProperty"))
// Here objects still contains the object that I have already deleted
// ...
}
}
}
Make sure you do this autoreleasepool { ... } wrap for any background thread where you create a Realm instance, primarily in the GCD.
If that still doesn't work, you can do:
myDeleteFunc(completion: {
DispatchQueue.main.async {
autoreleasepool {
let realm = try! Realm()
realm.refresh()
Realm isolates transactions on each thread to avoid changes from one thread immediately affect another. This mechanism also avoids the "faults" inherent to ORMs.
In your code, you can choose to refresh (advance) the realm to the latest state at points that you control and can handle data before and after the refresh being different.
Realms on a thread with a runloop (such as the main thread) auto-advance on every iteration of the runloop by default.
In your code sample, you invoke DispatchQueue.main.async immediately after a commit from another thread, which means that if you already have a Realm on the main thread, the async block will be at the same state and won't include the last commit.
But you can call Realm.refresh() explicitly at the start of your async block, which will ensure that this block sees that last commit:
myDeleteFunc(completion: {
DispatchQueue.main.async {
let realm = try! Realm()
realm.refresh()
let objects = Array(realm.objects(MyObject.self).sorted(byProperty: "aProperty"))
// ...
}
}
See Realm's docs on Seeing Changes From Other Threads for more information.

Data content changes when Coredata didsavenotification is received

I'm having a terrible problem. I'm saving a managedObject "Customer" with field sync as false using my main MOC. Then after syncing with server, I use a private MOC to save the Customer with sync field as true. When I save this, notification is passed and the main MOC saves the customer correctly. Then when I edit that customer, the same process happens. But this time, even if the sync field is true, upto the point where private MOC saves it, the notification got at the other end contains Customer with sync as false. If I repeat the process, sync becomes true. Instead of repeating, if I am saving some other data using the private MOC, then with that notification, Customer also comes with sync field as true. I don't understand why notification method is not working correctly. In addObserver of didsavenotification, I have specified the object as the private MOC itself. Somebody please help.
These are the code snippets prominent in this flow.
//Assigning false
selectedCustomer.synced = NSNumber(bool: false)
//Saving using main MOC
try managedObjectContext.save()
//Assigning synced details to a dictionary
let cust: CustomerDetails = CustomerDetails(data: customer)
cust.synced = true
//Assigning dictionary to customer managedObject fetched from coredata using private MOC
assignData(customerDetails, toCustomer: customer)
//Saving the private MOC
try syncManagedObjectContext.save()
//Getting notification and saving
managedObjectContext.performBlock { () -> Void in self.managedObjectContext.mergeChangesFromContextDidSaveNotification(notification)
}
//Registration of notification
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self, selector: #selector(AppDelegate.managedObjectContextDidSave(_:)), name: NSManagedObjectContextDidSaveNotification, object: syncManagedObjectContext)
EDIT:
These are the definitions of the MOCs I'm using
lazy var managedObjectContext: NSManagedObjectContext = {
let coordinator = self.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
managedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
lazy var syncManagedObjectContext: NSManagedObjectContext = {
let coordinator = self.managedObjectContext.persistentStoreCoordinator
var managedObjectContext = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
managedObjectContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
managedObjectContext.persistentStoreCoordinator = coordinator
return managedObjectContext
}()
Show how you defined your MOCs as well.
Are they parent child?
Are they siblings against the NSPersistentStoreCoordinator?
This can have an impact.
I would also turn on -com.apple.CoreData.ConcurrencyDebug 1 and run your app again as this sounds like a threading issue.
Update 1
As your NSManagedObjectContext instances are siblings you really need to turn on the currency debug flag and run again. This really reads like a threading issue.
Update 2
There is not a lot of code in your question to try and deduce an answer if this is not a threading issue.
My next suggestion would be to move your private MOC from a sibling to a child (Apple's documentation on threading discusses this) and stop using the notification (unnecessary with child MOCs).
If the issue is still there then you are going to need to trace it in the debugger and find out at what point the issue surfaces. It can still be a threading issue without being a "touched the wrong object on the wrong thread" issue. Is the property being changed when you think it is? Using the debugger is the only way to be certain.

Resources