NSOperation and NSOperationQueue working thread vs main thread
Solution 1
My question is whether the database write operation occur in main thread or in a background thread?
If you create an NSOperationQueue
from scratch as in:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
It will be in a background thread:
Operation queues usually provide the threads used to run their operations. In OS X v10.6 and later, operation queues use the libdispatch library (also known as Grand Central Dispatch) to initiate the execution of their operations. As a result, operations are always executed on a separate thread, regardless of whether they are designated as concurrent or non-concurrent operations
Unless you are using the mainQueue
:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
You can also see code like this:
NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
[myQueue addOperationWithBlock:^{
// Background work
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// Main thread work (UI usually)
}];
}];
And the GCD version:
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
// Background work
dispatch_async(dispatch_get_main_queue(), ^(void)
{
// Main thread work (UI usually)
});
});
NSOperationQueue
gives finer control with what you want to do. You can create dependencies between the two operations (download and save to database). To pass the data between one block and the other, you can assume for example, that a NSData
will be coming from the server so:
__block NSData *dataFromServer = nil;
NSBlockOperation *downloadOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakDownloadOperation = downloadOperation;
[weakDownloadOperation addExecutionBlock:^{
// Download your stuff
// Finally put it on the right place:
dataFromServer = ....
}];
NSBlockOperation *saveToDataBaseOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakSaveToDataBaseOperation = saveToDataBaseOperation;
[weakSaveToDataBaseOperation addExecutionBlock:^{
// Work with your NSData instance
// Save your stuff
}];
[saveToDataBaseOperation addDependency:downloadOperation];
[myQueue addOperation:saveToDataBaseOperation];
[myQueue addOperation:downloadOperation];
Edit: Why I am using __weak
reference for the Operations, can be found here. But in a nutshell is to avoid retain cycles.
Solution 2
If you want to perform the database writing operation in the background thread you need to create a NSManagedObjectContext
for that thread.
You can create the background NSManagedObjectContext
in the start method of your relevant NSOperation
subclass.
Check the Apple docs for Concurrency with Core Data.
You can also create an NSManagedObjectContext
that executes requests in its own background thread by creating it with NSPrivateQueueConcurrencyType
and performing the requests inside its performBlock:
method.
Solution 3
From NSOperationQueue
In iOS 4 and later, operation queues use Grand Central Dispatch to execute operations. Prior to iOS 4, they create separate threads for non-concurrent operations and launch concurrent operations from the current thread.
So,
[NSOperationQueue mainQueue] // added operations execute on main thread
[NSOperationQueue new] // post-iOS4, guaranteed to be not the main thread
In your case, you might want to create your own "database thread" by subclassing NSThread
and send messages to it with performSelector:onThread:
.
Solution 4
The execution thread of NSOperation depends on the NSOperationQueue
where you added the operation. Look out this statement in your code -
[[NSOperationQueue mainQueue] addOperation:yourOperation]; // or any other similar add method of NSOperationQueue class
All this assumes you have not done any further threading in main
method of NSOperation
which is the actual monster where the work instructions you have (expected to be) written.
However, in case of concurrent operations, the scenario is different. The queue may spawn a thread for each concurrent operation. Although it's not guarrantteed and it depends on system resources vs operation resource demands at that point in the system. You can control concurrency of operation queue by it's maxConcurrentOperationCount
property.
EDIT -
I found your question interesting and did some analysis/logging myself. I have NSOperationQueue created on main thread like this -
self.queueSendMessageOperation = [[[NSOperationQueue alloc] init] autorelease];
NSLog(@"Operation queue creation. current thread = %@ \n main thread = %@", [NSThread currentThread], [NSThread mainThread]);
self.queueSendMessageOperation.maxConcurrentOperationCount = 1; // restrict concurrency
And then, I went on to create an NSOperation and added it using addOperation. In the main method of this operation when i checked for current thread,
NSLog(@"Operation obj = %@\n current thread = %@ \n main thread = %@", self, [NSThread currentThread], [NSThread mainThread]);
it was not as main thread. And, found that current thread object is not main thread object.
So, custom creation of queue on main thread (with no concurrency among its operation) doesn't necessarily mean the operations will execute serially on main thread itself.
Solution 5
The summary from the docs is operations are always executed on a separate thread
(post iOS 4 implies GCD underlying operation queues).
It's trivial to check that it is indeed running on a non-main thread:
NSLog(@"main thread? %@", [NSThread isMainThread] ? @"YES" : @"NO");
When running in a thread it's trivial to use GCD/libdispatch to run something on the main thread, whether core data, user interface or other code required to run on the main thread:
dispatch_async(dispatch_get_main_queue(), ^{
// this is now running on the main thread
});
Related videos on Youtube
Zach
Updated on June 19, 2020Comments
-
Zach almost 4 years
I have to carry out a series of download and database write operations in my app. I am using the
NSOperation
andNSOperationQueue
for the same.This is application scenario:
- Fetch all postcodes from a place.
- For each postcode fetch all houses.
- For each house fetch inhabitant details
As said, I have defined an
NSOperation
for each task. In first case (Task1), I am sending a request to server to fetch all postcodes. The delegate within theNSOperation
will receive the data. This data is then written to database. The database operation is defined in a different class. FromNSOperation
class I am making a call to the write function defined in database class.My question is whether the database write operation occur in main thread or in a background thread? As I was calling it within a
NSOperation
I was expecting it to run in a different thread (Not MainThread) as theNSOperation
. Can someone please explain this scenario while dealing withNSOperation
andNSOperationQueue
.-
Cy-4AH over 10 yearsIf you add operations to main queue, then they will be performed in main thread. If you create your own NSOperationQueue and add operations to it, then they will be performed in threads of this queue.
-
Brad Allred over 10 yearsI dont think you are going to get a better answer than @Cy-4AH gave unless you get more specific/post some code. I will say you can always put a breakpoint in the code and when it trips it will show you what thread the trace is in.
-
Jeffery Thomas over 10 yearsWhat does "The delegate within the NSOperation will receive the data." mean? Neither
NSOperation
norNSOperationQueue
contain delegate properties. -
Wain over 10 yearsYou can also push your delegate call onto the main thread rather than making any assumption about the current thread...
-
Nikolai Ruhe over 10 yearsWhat makes you think this question is related to Core Data?
-
Nikolai Ruhe over 10 yearsThe question does not mention a specific database, like fmdb or sqlite.
-
Holly over 10 yearsThat is correct, I made an assumption based upon context. If the app were connecting to a multi-tenant, thread-safe database, such as MySQL, the question wouldn't make sense. There are other single-user databases that can be used, but SQLite is the most common by far.
-
Gianluca P. over 10 yearsthe answer is correct but please note that the correct line of code is:
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
and that the NSOperationQueue on main thread cannot be suspended. -
Michael Waterfall almost 10 years@jacky-boy Out of interest, why the use of the weak references to the downloadOperation and saveToDataBaseOperation in the final code snippet?
-
Pavan over 9 yearsRuiAAPeres, hey I'm curious why there are weak references being used in the final code snippet? Could you shed some light on that please?
-
Rui Peres over 9 years@Pavan the idea is that if you happen to reference the same block operation inside its own block, you got a retain cycle. For reference: conradstoll.com/blog/2013/1/19/…
-
Pavan over 9 yearsso which block is the same as the block that runs inside it?
-
Rui Peres over 9 yearsDepends... It could be either the
saveToDataBaseOperation
or thedownloadOperation
. Again, this is just a precaution measure, if you are sure you won't have retain cycles, you don't need to make it__weak
. -
ikanimo over 9 yearsthanks alot... u save my life: [NSOperationQueue new] // post-iOS4, guaranteed to be not the main thread
-
Gordonium about 6 yearsI believe using
[[NSOperationQueue alloc] init]
assures that the queue will use an underlying default background global DispatchQueue. So any custom initialised OperationQueue will feed its tasks onto background DispatchQueues and therefore feed tasks into threads that are not the main thread. To feed operations into the main thread you would have to obtain the main OperationQueue using something like[NSOperationQueue mainQueue]
. This obtains the main operation queue that uses the main DispatchQueue internally and therefore eventually feeds tasks onto the main thread.