2012-11-26 62 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的工作。這兩個隊列似乎並不獨立。 –

相關問題