A common source of confusion when implementing ContentProviders is that of thread-safety. We all know that any potentially expensive query should be asynchronous so as not to block the UI thread, but when, if ever, is it OK to make calls to the ContentProvider from multiple threads?
Threads and ContentProviders
The documentation on ContentProviders warns that its methods may be called from multiple threads and therefore must be thread-safe:
Data access methods (such as insert(Uri, ContentValues) and update(Uri, ContentValues, String, String[])) may be called from many threads at once, and must be thread-safe.
In other words, Android does not synchronize access to the ContentProvider for you. If two calls to the same method are made simultaneously from separate threads, neither call will wait for the other. Requiring the client to deal with concurrency themselves makes sense from a framework developer's point of view. The abstract ContentProvider class cannot assume that its subclasses will require synchronization, as doing so would be horribly inefficient.
Ensuring Thread Safety
So now that we know that the ContentProvider is not thread safe, what do we need to do in order to eliminate potential race conditions? Just make every method synchronized, right?
Well... no, not necessarily. Consider a ContentProvider that uses a SQLiteDatabase as its backing data source. As per the documentation, access to the SQLiteDatabase is synchronized by default, thus guaranteeing that no two threads will ever touch it at the same time. In this case, synchronizing each of the ContentProvider's methods is both unnecessary and costly. Remember that a ContentProvider serves as a wrapper around the underlying data source; whether or not you must take extra measures to ensure thread safety often depends on the data source itself.
Conclusion
Although the ContentProvider lacks in thread-safety, often times you will find that no further action is required on your part with respect to preventing potential race conditions. The canonical example is when your ContentProvider is backed by a SQLiteDatabase; when two threads attempt to write to the database at the same time, the SQLiteDatabase will lock itself down, ensuring that one will wait until the other has completed. Each thread will be given mutually exclusive access to the data source, ensuring the thread safety is met.
This has been a rather short post, so don't hesitate to leave a comment if you have any clarifying questions. Don't forget to +1 this post below if you found it helpful! :)

If there is a race condition for files, a solution can be to always use FileLock to protect file operation within CPs.
ReplyDeleteCan someone look into this ?
That sounds like the correct solution to me. The important thing to understand is that the ContentProvider won't do any of the synchronization for you. If your ContentProvider is backed by raw files, then you'd need to serialize access to them yourself.
DeleteIn my app, I had a ContentProvider backed by SQLite and filled by an IntentService, which downloaded stuff from the Internet. The ContentProvider was always triggered concurrently by the UI LoaderManagers (multiple Fragments calling the same data in an Activity).
ReplyDeleteSynchronizing these ContentProvider calls made everything slower. Noticeably slower.
However, the IntentService was being triggered twice, and any missing data would be marked to be downloaded twice. And inserted twice. At first, I used this answer:
http://stackoverflow.com/a/8690698/489607
And I'm still using just in case, to avoid the ultimate deadly mistake of duplicated data. If anything, I can count on SQlite itself to safeguard it.
But I still had to solve the duplicated (and discarded) downloads. To that, I implemented a single history of what was downloaded, together with a very simple database checker. If the second call asked for something that was included in the first call (and if not, if it was at least included in the db already), only the remaining stuff would be marked for download and inserted. Together with ContentProvider and Cursors, everything went perfectly fine.
It's obviously doable, but at least to me, putting a ContentProvider together with Services and UIs, and orchestrating all of that together is not exactly trivial (especially when you need notifications and track loading states between all this), and I admit sometimes I have no idea of what happens behind the scenes.
But since I'm not wasting users mobile data or inserting duplicated entries, while still enjoying speedy multithreaded queries, it's OK in my book! :-)
Have you put this theory to the test? In my experience, you do need to synchronize even though you use SQLiteDatabase.
ReplyDeleteIn my app, I have a SyncAdapter which uses the ContentProvider to put data into the SQLiteDatabase. Everythint was working perfectly for months. After I have added a second way to update the ContentProvider from GCM push notifications, I start getting crash reports from some users like
android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 3850)
OR
android.database.sqlite.SQLiteDiskIOException: disk I/O error (code 778)
and several other exceptions related to sqlite.
And I haven't found a good solution yet.
Those exceptions don't sound like they are related to threads/concurrency. My guess is that you are doing something else incorrect in your code... from StackOverflow it seems that some people got this exception by forgetting to close their cursors, for example. If you want to post a question on StackOverflow about this with your code, you can leave a link to the post as a reply here and I can take a look. :)
Delete