I have an application in which a long running process (> 1 min) is placed onto an NSOperationQueue (Queue A). The UI is fully-responsive while the Queue A operation runs, exactly as expected.
However, I have a different kind of operation the user can perform which runs on a completely separate NSOperationQueue (Queue B).
When a UI event triggers the placement of an operation on Queue B, it must wait until after the currently-executing operation on Queue A finishes. This occurs on an iPod Touch (MC544LL).
What I expected to see instead was that any operation placed onto Queue B would more or less begin immediately executing in parallel with the operation on Queue A. This is the behavior I see on the Simulator.
My question is two parts:
- Is the behavior I'm seeing on my device to be expected based on available documentation?
- Using NSOperation/NSOperationQueue, how do I pre-empt the currently running operation on Queue A with a new operation placed on Queue B?
Note: I can get exactly the behavior I'm after by using GCD queues for Queues A/B, so I know my device is capable of supporting what I'm trying to do. However, I really, really want to use NSOperationQueue because both operations need to be cancelable.
I have a simple test application:
The ViewController is:
//
// ViewController.m
// QueueTest
//
#import "ViewController.h"
@interface ViewController ()
@property (strong, nonatomic) NSOperationQueue *slowQueue;
@property (strong, nonatomic) NSOperationQueue *fastQueue;
@end
@implementation ViewController
-(id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
self.slowQueue = [[NSOperationQueue alloc] init];
self.fastQueue = [[NSOperationQueue alloc] init];
}
return self;
}
-(void)viewDidLoad
{
NSLog(@"View loaded on thread %@", [NSThread currentThread]);
}
// Responds to "Slow Op Start" button
- (IBAction)slowOpStartPressed:(id)sender {
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
[self workHard:600];
}];
[self.slowQueue addOperation:operation];
}
// Responds to "Fast Op Start" button
- (IBAction)fastOpStart:(id)sender {
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
[operation addExecutionBlock:^{
NSLog(@"Fast operation on thread %@", [NSThread currentThread]);
}];
[self.fastQueue addOperation:operation];
}
-(void)workHard:(NSUInteger)iterations
{
NSLog(@"SlowOperation start on thread %@", [NSThread currentThread]);
NSDecimalNumber *result = [[NSDecimalNumber alloc] initWithString:@"0"];
for (NSUInteger i = 0; i < iterations; i++) {
NSDecimalNumber *outer = [[NSDecimalNumber alloc] initWithUnsignedInteger:i];
for (NSUInteger j = 0; j < iterations; j++) {
NSDecimalNumber *inner = [[NSDecimalNumber alloc] initWithUnsignedInteger:j];
NSDecimalNumber *product = [outer decimalNumberByMultiplyingBy:inner];
result = [result decimalNumberByAdding:product];
}
result = [result decimalNumberByAdding:outer];
}
NSLog(@"SlowOperation end");
}
@end
The output I see after first pressing the "Slow Op Start" button followed ~1 second later by pressing the "Fast Op Start" button is:
2012-11-28 07:41:13.051 QueueTest[12558:907] View loaded on thread <NSThread: 0x1d51ec30>{name = (null), num = 1}
2012-11-28 07:41:14.745 QueueTest[12558:1703] SlowOperation start on thread <NSThread: 0x1d55e5f0>{name = (null), num = 3}
2012-11-28 07:41:25.127 QueueTest[12558:1703] SlowOperation end
2012-11-28 07:41:25.913 QueueTest[12558:3907] Fast operation on thread <NSThread: 0x1e36d4c0>{name = (null), num = 4}
As you can see, the second operation does not begin executing until after the first operation finishes, despite the fact that these are two separate (and presumably independent) NSOperationQueues.
I have read the Apple Concurrency Guide, but find nothing describing this situation. I've also read two SO questions on related topics (link, link), but neither seems to get to the heart of the problem I'm seeing (pre-emption).
Other things I've tried:
- setting the queuePriority on each NSOperation
- setting the queuePriority on each NSOperation while placing both types of operations onto the same queue
- placing both operations onto the same queue
This question has undergone multiple edits, which may make certain comments/answers difficult to understand.