< Previous PageNext Page > Hide TOC

Multi-Threading with Core Data

There may be perceived performance advantages that accrue from using multiple threads with Core Data. In particular, if you execute a large or complex fetch that takes some time, you might execute the full fetch on a background thread. It is important to consider, however, that most of the Application Kit is not thread safe and that you need to take considerable care that object graphs do not get into an inconsistent state.

Contents:

Thread Safety Fundamentals
General Guidelines
Locking
Fetching in a Background Thread
Saving


Thread Safety Fundamentals

There are several issues to bear in mind when using multi-threading in a Core Data application:

General Guidelines

In general, you should not use any given managed object or managed object context in more than one thread. Instead, you should give each thread its own entirely private managed object context and keep their associated object graphs separated on a per-thread basis. If you do this, there is no need to lock contexts during access. You can use one persistent store coordinator per group of cooperating threads (for example, for your application or for each document).

There are three patterns you can adopt to support multi-threading in a Core Data application; in order of preference they are:

  1. Create a separate managed object context for each thread and share a single persistent store coordinator.

    If you need to “pass” managed objects between threads, you just pass their object IDs.

    If you want to aggregate a number of operations in one context together as if a virtual single transaction, you can lock the persistent store coordinator to prevent other managed object contexts using the persistent store coordinator over the scope of several operations.

  2. Create a separate managed object context and persistent store coordinator for each thread.

    If you need to “pass” managed objects between threads, you just pass their object IDs.

    Using a separate persistent store coordinator for each thread allows for completely concurrent operations.

  3. Pass managed objects or managed object contexts between threads.

    This approach is strongly discouraged. You must ensure that you apply locks as appropriate and necessary.

Locking

Generally, you only need to lock a managed object context (and not even then if you ensure that each thread has its own private context, as described in “General Guidelines”). If you do choose to share a managed object context or a persistent store coordinator between threads, you must ensure that any method invocations are made from a thread-safe scope. For locking, you should use the NSLocking methods on managed object context and persistent store coordinator instead of implementing your own mutexes. These methods help provide contextual information to the framework about the application's intent—that is, in addition to providing a mutex, they help scope clusters of operations.

Typically you lock the context or coordinator using tryLock or lock. If you do this, the framework will ensure that what it does behind the scenes is also thread-safe. For example, if you create one context per thread, but all pointing to the same persistent store coordinator, Core Data takes care of accessing the coordinator in a thread-safe way (NSManagedObjectContext's lock and unlock methods handle recursivity).

If you lock (or successfully tryLock) a context, that context must be retained until you invoke unlock. If you don’t properly retain a context in a multi-threaded environment, you may cause a deadlock.

Fetching in a Background Thread

One of the simplest multi-threading techniques you can use with Core Data to improve application responsiveness is to execute a fetch request on a background thread. (Note that this technique is only useful if you are using an SQLite store, since data from binary and XML stores is read into memory immediately on open.) This means that if a fetch is complicated or returns a large amount of data, you can return control to the user and display results as they arrive. For an example of how to do this, see the BackgroundFetching example in /Developer/Examples/CoreData/.

You use two managed object contexts associated with a single persistent store coordinator. You fetch in one managed object context on a background thread, and pass the object IDs of the fetched objects to another thread. In the second thread (typically the application's main thread, so that you can then display the results), you use the second context to fault in objects with those object IDs (you use objectWithID: to instantiate the object).

Saving

Performing a save operation in a detached thread is error-prone unless you take additional steps to prevent the application from quitting before the save is completed. Specifically, all NSThread-based threads are "detached" (see the documentation for pthread for complete details) and a process runs only until all not-detached threads have exited. The work a detached thread is performing is therefore considered optional, and the process may terminate at any time. (Most users do not consider saving to be optional work!) In Cocoa, only the main thread is not-detached. If you need to save on other threads, you must write additional code such that the main thread prevents the application from quitting until all the save operation is complete.



< Previous PageNext Page > Hide TOC


© 2004, 2009 Apple Inc. All Rights Reserved. (Last updated: 2009-03-04)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.