gfrigon
gfrigon

Reputation: 2267

How to prevent IOS concurrent NSOperation from running on main thread

I am writing an application that periodically fetches data from a web server using ASI HTTP and then processes that data to display something relevant to the user on the UI. The data is retrieved from different requests on a single server. The data itself needs to be processed in a specific order. One of the blocks of data is much bigger than the other ones.

In order not to lock the UI while the data is being processed, I have tried to use the NSOperationQueue to run the data processing on different threads. This works fine about 90% of the times. However, in the remaining 10% of the time, the biggest block of data is being processed on the main thread, which cause the UI to block for 1-2 seconds. The application contains two MKMapViews in different tabs. When both MKMapViews tabs are loaded the percentage of time the biggest block of data is being processed on the main thread increases above 50% (which seems to point to the assumption that this happens when there is more concurrent activity).

Is there a way to prevent the NSOperationQueue to run code on the main thread?

I have tried to play with the NSOperationQueue –setMaxConcurrentOperationCount:, increasing and decreasing it but there was no real change on the issue.

This is the code that starts the periodic refresh:

- (void)refreshAll{    

    // Create Operations
    ServerRefreshOperation * smallDataProcessor1Op = [[ServerRefreshOperation alloc] initWithDelegate:_smallDataProcessor1];
    ServerRefreshOperation * smallDataProcessor2Op = [[ServerRefreshOperation alloc] initWithDelegate:_smallDataProcessor2];
    ServerRefreshOperation * smallDataProcessor3Op = [[ServerRefreshOperation alloc] initWithDelegate:_smallDataProcessor3];
    ServerRefreshOperation * smallDataProcessor4Op = [[ServerRefreshOperation alloc] initWithDelegate:_smallDataProcessor4];
    ServerRefreshOperation * smallDataProcessor5Op = [[ServerRefreshOperation alloc] initWithDelegate:_smallDataProcessor5];
    ServerRefreshOperation * hugeDataProcessorOp = [[ServerRefreshOperation alloc] initWithDelegate:_hugeDataProcessor];

    // Create dependency graph (for response processing)
    [HugeDataProcessorOp addDependency:smallDataProcessor4Op.operation];
    [smallDataProcessor5Op addDependency:smallDataProcessor4Op.operation];
    [smallDataProcessor4Op addDependency:smallDataProcessor3Op.operation];
    [smallDataProcessor4Op addDependency:smallDataProcessor2Op.operation];
    [smallDataProcessor4Op addDependency:smallDataProcessor1Op.operation];

    // Start be sending all requests to server (startAsynchronous directly calls the ASIHTTPRequest startAsynchronous method)
    [smallDataProcessor1Op startAsynchronous];
    [smallDataProcessor2Op startAsynchronous];
    [smallDataProcessor3Op startAsynchronous];
    [smallDataProcessor4Op startAsynchronous];
    [smallDataProcessor5Op startAsynchronous];
    [hugeDataProcessorOp startAsynchronous];
}

This is the code that sets the ASI HTTP completion block that starts the data processing:

[_request setCompletionBlock:^{
    [self.delegate setResponseString:_request.responseString];
    [[MyModel queue] addOperation:operation]; // operation is a NSInvocationOperation that calls the delegate parse method
}];

I have added this block of code in all NSInvocationOperation Invoked method at the entry point:

if([NSThread isMainThread]){
    NSLog(@"****************************Running <operation x> on Main thread");
}

The line is printed every time the UI freezes. This shows that the whole operation is run on the main thread. It is actually always the hugeDataProcessorOp that is run on the main thread. I assume that this is because it is the operation that always receives its answer last from the server.

Upvotes: 4

Views: 1962

Answers (2)

gfrigon
gfrigon

Reputation: 2267

After much investigation in my own code, I can confirm that this was a coding error.

There was an old call remaining that did not go through the NSInvocationOperation but was calling the selector that NSInvocationOperation should have called directly (therefore not using the concurrent NSOperationQueue.

This means that the NSOperationQueue DOES NOT use the main thread (except if it is the one retrieved by +mainQueue).

Upvotes: 4

Chris Wagner
Chris Wagner

Reputation: 21003

Override isConcurrent on your NSOperation and return YES. According to the documentation this will cause your NSOperation to be run asynchronously.

Upvotes: 0

Related Questions