2012-11-26 65 views
2

我有一个应用程序将长时间运行的进程(> 1分钟)放到NSOperationQueue(队列A)上。在队列A操作运行时,UI完全响应,完全如预期。把一个NSOperationQueue与NSOperation放在一个单独的NSOperationQueue上抢占NSOperation?

但是,我有一种不同的操作,用户可以在完全独立的NSOperationQueue(队列B)上执行这些操作。

当UI事件触发队列B上的操作的放置时,它必须等待直到队列A上的当前正在执行的操作完成后。这发生在iPod Touch(MC544LL)上。

我期望看到的是,任何放置到队列B上的操作都会或多或少地开始立即与队列A上的操作并行执行。这是我在模拟器上看到的行为。

我的问题是两个部分:

  • 是我看到我的设备上的行为是基于现有文件预期?
  • 使用NSOperation/NSOperationQueue,我如何通过在队列B上放置一个新的操作来预占队列A上的当前正在运行的操作?

注意:通过使用排队队列A/B的GCD队列,我可以得到完全的行为,所以我知道我的设备能够支持我想要做的事情。不过,我真的很想使用NSOperationQueue,因为这两个操作都需要被取消。

我有一个简单的测试应用程序:

enter image description here

的视图控制器是:

// 
// 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 

予后第一次按下“慢运算开始”按钮看到的输出,接着约1秒后按“快速运算开始”按钮是:

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} 

正如你所看到的,第二个操作直到第一个操作完成后才开始执行,尽管事实上这是两个独立的(并且大概是独立的)NSOperationQueues。

我已阅读Apple Concurrency Guide,但没有发现任何描述这种情况的内容。我还阅读了两个关于相关主题的SO问题(link,link),但似乎都没有涉及到我看到的问题(抢先)。

其他的事情我已经试过:

  • 设定每个的NSOperation
  • 的queuePriority设置queuePriority每个的NSOperation同时将两种类型的操作在同一个队列
  • 将两种操作到同一队列

此问题已经过多次编辑,可能会使某些评论/回答难以理解。

+0

我看到你治好了症状,而我键入我的答案:-)我怀疑如果你需要NSOperationQueue的操作管理设施,并且不能使用GCD,让缓慢的操作队列序列将做你需要的。 –

+0

@SimonLawrence,是的 - 我们的更新越过了路径:)。不幸的是,使用[slowQueue setMaxConcurrentOperationCount:1]创建队列串并没有帮助。有另一种方法吗? –

+0

在你的问题的代码中,你将两个操作添加到同一个队列。错字或所有问题的原因? – jrturton

回答

1

我怀疑你遇到的问题是两个操作队列都在底层的默认优先级调度队列上执行它们的块。因此,如果几个缓慢的操作在快速操作之前排队,那么你可能会看到这种行为。

为什么不为慢操作设置NSOperationQueue实例,以便它在任何给定时间只执行一个操作(即,将maxConcurrentOperationCount设置为此队列的一个操作),或者如果您的操作都是块,那么为什么不使用GCD队列直?例如

static dispatch_queue_t slowOpQueue = NULL; 
static dispatch_queue_t fastOpQueue = NULL; 

static dispatch_once_t onceToken; 
dispatch_once(&onceToken, ^{ 
    slowOpQueue = dispatch_queue_create("Slow Ops Queue", NULL); 
    fastOpQueue = dispatch_queue_create("Fast Ops Queue", DISPATCH_QUEUE_CONCURRENT); 
}); 

for (NSUInteger slowOpIndex = 0; slowOpIndex < 5; slowOpIndex++) { 
    dispatch_async(slowOpQueue, ^(void) { 
     NSLog(@"* Starting slow op %d.", slowOpIndex); 
     for (NSUInteger delayLoop = 0; delayLoop < 1000; delayLoop++) { 
      putchar('.'); 
     } 

     NSLog(@"* Ending slow op %d.", slowOpIndex); 
    }); 
} 

for (NSUInteger fastBlockIndex = 0; fastBlockIndex < 10; fastBlockIndex++) { 
    dispatch_async(fastOpQueue, ^(void) { 
     NSLog(@"Starting fast op %d.", fastBlockIndex); 
     NSLog(@"Ending fast op %d.", fastBlockIndex); 
    }); 
} 

至于使用NSOperationQueue按你的意见对需要操作取消设施等,你可以尝试:

- (void)loadSlowQueue 
{ 
    [self.slowQueue setMaxConcurrentOperationCount:1]; 
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ 
     NSLog(@"begin slow block 1"); 

     [self workHard:500]; 

     NSLog(@"end slow block 1"); 
    }]; 

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{ 
     NSLog(@"begin slow block 2"); 

     [self workHard:500]; 

     NSLog(@"end slow block 2"); 
    }]; 

    [self.slowQueue addOperation:operation]; 
    [self.slowQueue addOperation:operation2]; 
} 

当我想到了两个街区您添加到操作上慢队列正在默认队列上并行执行,并阻止您的快速操作被调度。

编辑:

如果你还在寻找默认GCD队列呛,为什么不创建的NSOperation子类,不使用GCD在大家的缓慢操作执行块,这还是会给予你的声明便利不为每个操作创建单独的子类,而是使用常规NSOperation的线程模型。例如

#import <Foundation/Foundation.h> 

typedef void (^BlockOperation)(NSOperation *containingOperation); 

@interface PseudoBlockOperation : NSOperation 

- (id)initWithBlock:(BlockOperation)block; 
- (void)addBlock:(BlockOperation)block; 

@end 

然后为实现:

#import "PseudoBlockOperation.h" 

@interface PseudoBlockOperation() 

@property (nonatomic, strong) NSMutableArray *blocks; 

@end 

@implementation PseudoBlockOperation 

@synthesize blocks; 

- (id)init 
{ 
    self = [super init]; 

    if (self) { 
     blocks = [[NSMutableArray alloc] initWithCapacity:1]; 
    } 

    return self; 
} 

- (id)initWithBlock:(BlockOperation)block 
{ 
    self = [self init]; 

    if (self) { 
     [blocks addObject:[block copy]]; 
    } 

    return self; 
} 

- (void)main 
{ 
    @autoreleasepool { 
     for (BlockOperation block in blocks) { 
      block(self); 
     } 
    } 
} 

- (void)addBlock:(BlockOperation)block 
{ 
    [blocks addObject:[block copy]]; 
} 

@end 

然后在你的代码,你可以这样做:

PseudoBlockOperation *operation = [[PseudoBlockOperation alloc] init]; 
[operation addBlock:^(NSOperation *operation) { 
    if (!operation.isCancelled) { 
     NSLog(@"begin slow block 1"); 

     [self workHard:500]; 

     NSLog(@"end slow block 1"); 
    } 
}]; 

[operation addBlock:^(NSOperation *operation) { 
    if (!operation.isCancelled) { 
     NSLog(@"begin slow block 2"); 

     [self workHard:500]; 

     NSLog(@"end slow block 2"); 
    } 
}]; 

[self.slowQueue addOperation:operation]; 

注意,在这个例子中被添加到相同的任何块操作将按顺序执行而不是并行执行,以便同时为每个块创建一个操作。这比NSBlockOperation的优势在于,您可以通过更改BlockOperation的定义将参数传递到块中 - 在这里我传递了包含操作,但您可以传递其他所需的上下文。

希望有所帮助。

+0

这是朝正确方向迈出的一步,但由于我的编辑(与您的答案交叉的路径)表明,我需要能够取消所有操作。据我所知,这是不可能的内置GCD功能。 –

+0

我刚刚注意到在你的例子中,你将两个慢块添加到同一个NSOperation中,我相信这意味着即使NSOperationQueue现在设置为串行,它们也会在GCD队列中并行退出,你可以尝试添加setMaxConcurrentOperations = 1修改loadSlowQueue,以便它为每个块添加一个操作,而不是将它们都放在同一个操作中? –

+0

在viewDidLoad中添加[self.slowQueue setMaxConcurrentOperationCount:1]的结果与张贴结果相同。主要问题依然存在:fastQueue完全被阻塞,直到完成slowQueue的工作。这两个队列似乎并不独立。 –

相关问题