1

I have an iOS application that has an NSTimer which fires every 5 seconds. This then posts a notification, telling several controllers that they must now recalculate some data and then update their UI.

When this all happens on the main thread, scrollviews can become jittery as the data is processed.

Using GCD, I have wrapped the code called when a notification is posted:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  // Code here
  dispatch_async(dispatch_get_main_queue(), ^(){
    // Reload tableviews and UI
  };
};

Unfortunately, this leads in certain cases to several threads accessing the same instance variables. This causes my application to crash as sometimes it ends up mutating an array being enumerated in another thread.

Wrapping the code in a synchronised block prevents this.

When the user scrolls the tableview, several methods are called to calculate the height etc, on the main thread. The code within the background thread is working on the code needed by the main thread. As such, this can usually further cause crashes

I have tried setting the variables to 'atomic', but this doesn't solve the issue. Ideally, I don't want to have the main thread waiting on the background thread, but I am not sure how to best resolve this issue given that they need the same data?

  • You've got a tangled weave of threading and interconnected state, the solution is therefore less interconnectedness. Nothing about this kind of architecture speaks to the modularity potential of OO. Decouple things, make copies of data rather than trying to depend on state from other views. If you have to depend on their state, clearly this is meant to be sequential, rather than parallel. – CodaFi Mar 30 '14 at 13:13
  • 2
    Create an object to manage your data model. Let the update threads modify the model and have the model notify when an update is complete. View controllers can listen for that notification and refresh views accordingly. – Phillip Mills Mar 30 '14 at 13:33

1 Answers1

0

This is pretty classic multithreaded programming issues. There are a number of ways to solve it with basic locks (@synchronized blocks), reader/writer locks, etc but the problem is often that you can't control when the user is going to scroll or take other action. If you @synchronize, you have to do it anywhere that data is touched, including your UITableView data source methods. That can lead to stuttering if the background processing happens to be in the middle of something.**

Personally, I would use an immutable snapshot mechanism.

Have the background thread produce the results, then include just the data the UI needs to display in the notification data as an immutable snapshot (copy). That way the background thread never modifies the data the UI is currently reading for display. How you would implement this is highly dependent on how much data you are talking about and the form it takes, but the safe way would be to have copies of your classes with readonly properties. Alternatively, you can use a "frozen" flag. Make a copy, then set frozen = YES on the copy. The UI thread will only ever see "frozen" or readonly objects coming from the background thread.

The benefit is the UI never causes the background thread to stall and there are no locks required. The downside is increased memory usage, though if the amount of data is large you can use copy-on-write mechanisms to allow the background thread and UI thread to share the data, even though logically the UI thread has a completely separate copy.

** Note: In most applications, you don't have this sort of continuous background processing going on so those apps can use simpler mechanisms. This is typically a form of message passing where the background thread finishes its task and "passes the message" to the UI thread (passes the results). At that point the background work is finished so there is no concurrent modification happening.

russbishop
  • 16,587
  • 7
  • 61
  • 74