Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Demystify NSOperation: concurrent vs non-concurrent and async pattern

Yes, I know. There are a lot of questions and answers about the NSOperation world but I'm still having some doubts. Il' try to explain my doubts with a two parts question. They are related each other.

In the SO post nsoperationqueue-and-concurrent-vs-non-concurrent, Darren wrote that

A "concurrent" operation is concurrent on its own; it doesn't need NSOperationQueue to create a thread for it.

But searching a little bit, I've found that a NSOperation, even if it is declared concurrent (by means of overriding the isConcurrent method such as it returns YES), can be added to NSOperationQueue. What does this mean? If I add a concurrent NSOperation to a queue, what is going on under the hood? On the contrary, what happens if I use a concurrent operation as is (without adding it to a queue)?

The note taken from Apple doc is clear:

...operation queues ignore the value returned by isConcurrent and always call the start method of your operation from a separate thread. ...In general, if you are always using operations with an operation queue, there is no reason to make them concurrent.

Then, I'm really interested about using async pattern in NSOperation. I've found a good tutorial by Dave Dribin (concurrent operations). I got the overall meaning of his post.

You cannot use an async pattern (e.g. using an async NSURLConnection request) since delegates could not be called. When the main finishes the operation is removed. The solution is to override the start method to control the operation lifecycle...And dealing with run loops could be a pain.

Now, trying to understand his post, my doubt is about the need to run the start method in the main thread.

- (void)start
{
    if (![NSThread isMainThread])
    {
        [self performSelectorOnMainThread:@selector(start) withObject:nil waitUntilDone:NO];
        return;
    }

    // other code here...
}

When dealing with asynchronous APIs, we can begin the asynchronous call on the main thread in start and keep the operation running until it finishes.

Could you explain me why?

Thank you in advance.

like image 699
Lorenzo B Avatar asked Jun 02 '12 14:06

Lorenzo B


2 Answers

In my opinion, the isConcurrent property of NSOperation is confusingly named. It really means "is-asynchronous". That is, when its -start is invoked, does it return quickly whether or not the operation has run to completion (asynchronous)? Or does it not return until the operation has run to completion (synchronous)?

As Apple's docs state, this doesn't much matter when an operation is queued to an NSOperationQueue, since the queue invokes it on a worker thread, regardless. If it is synchronous, then that worker thread will be devoted to that operation until it completes. If it is asynchronous, then -start will return before the operation has completed and the worker thread can go on to do other work.

The question is, how does an asynchronous -start method make sure the work of the operation continues? That could entail spawning a separate thread to do the work, but that's silly. Better to let NSOperationQueue handle the threading.

More likely, it uses a run loop source that's driven by external events. NSURLConnection in its asynchronous mode is that sort of thing. But, in that case, it has to be sure the thread on which it schedules the run loop source a) will stick around, and b) will run its run loop. The worker threads of NSOperationQueue can't be relied upon to do either.

Again, you could create your own thread for each such operation specifically to stick around and run its run loop, but that's unnecessary and, again, offers no advantage over leaving the operation synchronous and queuing it.

One thread which you already know will stick around and run its run loop is the main thread. So, it is often best to schedule the run loop source on the main thread's run loop. The only thing to be careful of is that, in response to the external events that trigger the run loop source's handlers, you don't do any long-running work on the main thread. So, for example, when NSURLConnection calls your delegate methods with received data, you don't do expensive calculations on that data – or, if you have to, move that expensive computation to another thread.

Another possibility, a middle ground, is to create one thread of your own to be the worker for many asynchronous operations. So, rather than using the main thread or a thread per operation, you use a single thread whose job is just to sit around parked in its run loop. All of your asynchronous operations would schedule themselves on that thread's run loop. There's not really much need for or advantage to this approach, though.

like image 128
Ken Thomases Avatar answered Oct 16 '22 10:10

Ken Thomases


I think Dave addresses his motivation for shunting the start method onto the main thread in the post:

Update 2009-09-13: This is no longer true as of 10.6. The start method is always called on a background thread as of 10.6. To work properly with main-thread only and asynchronous APIs that rely on the run loop, we need to shunt our work over to the main thread. More on this in a followup post.

like image 1
Chris Gummer Avatar answered Oct 16 '22 11:10

Chris Gummer