首页 > 解决方案 > NSBlockOperation 是否可以在执行时自行取消,从而取消依赖的 NSOperation?

问题描述

我有许多NSBlockOperation具有依赖关系的链。如果链中早期的一项操作失败 - 我希望其他操作不运行。根据文档,这应该很容易从外部完成 - 如果我取消一个操作,所有相关的操作都应该自动取消。

但是 - 如果只有我的操作的执行块“知道”它在执行时失败了 - 它可以cancel自己工作吗?

我尝试了以下方法:

    NSBlockOperation *op = [[NSBlockOperation alloc] init];
    __weak NSBlockOperation *weakOpRef = op;
    [takeScreenShot addExecutionBlock:^{
        LOGInfo(@"Say Cheese...");
        if (some_condition == NO) { // for some reason we can't take a photo
            [weakOpRef cancel];
            LOGError(@"Photo failed");
        }
        else {
            // take photo, process it, etc.
            LOGInfo(@"Photo taken");
        }
    }];

op但是,当我运行它时,即使op已取消,也会执行依赖于的其他操作。由于它们是依赖的 - 它们肯定不会在op完成之前开始,并且我(在调试器和使用日志中)验证了块返回之前的isCancelled状态。队列仍然像成功完成一样执行它们。opYESop

然后我进一步挑战了文档,如下所示:

    NSOperationQueue *myQueue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *op = [[NSBlockOperation alloc] init];
    __weak NSBlockOperation *weakOpRef = takeScreenShot;
    [takeScreenShot addExecutionBlock:^{
        NSLog(@"Say Cheese...");
        if (weakOpRef.isCancelled) { // Fail every once in a while...
            NSLog(@"Photo failed");
        }
        else {
            [NSThread sleepForTimeInterval:0.3f];
            NSLog(@"Photo taken");
        }
    }];
    
    NSOperation *processPhoto = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"Processing Photo...");
        [NSThread sleepForTimeInterval:0.1f]; // Process  
        NSLog(@"Processing Finished.");
    }];
    
    // setup dependencies for the operations.
    [processPhoto addDependency: op];
    [op cancel];    // cancelled even before dispatching!!!
    [myQueue addOperation: op];
    [myQueue addOperation: processPhoto];
    
    NSLog(@">>> Operations Dispatched, Wait for processing");
    [eventQueue waitUntilAllOperationsAreFinished];
    NSLog(@">>> Work Finished");

但是很震惊地在日志中看到以下输出:

2020-11-05 16:18:03.803341 >>> Operations Dispatched, Wait for processing
2020-11-05 16:18:03.803427 Processing Photo...
2020-11-05 16:18:03.813557 Processing Finished.
2020-11-05 16:18:03.813638+0200 TesterApp[6887:111445] >>> Work Finished

请注意:取消的操作从未运行过 - 但依赖项processPhoto已执行,尽管它依赖于op.

任何人的想法?

标签: objective-cmacosobjective-c-blocksnsoperationqueuensoperation

解决方案


好的。我想我解开了这个谜。我只是误解了[NSOperation cancel] 文档

它说:

在 macOS 10.6 及更高版本中,如果操作在队列中但正在等待未完成的相关操作,则这些操作随后将被忽略。因为它已经被取消了,所以这种行为允许操作队列更快地调用操作的 start 方法并将对象从队列中清除。如果取消不在队列中的操作,此方法会立即将该对象标记为已完成。在每种情况下,将对象标记为就绪或完成会导致生成适当的 KVO 通知。

我认为如果操作 B 依赖于操作 A - 这意味着当 A 被取消时(因此 - 没有完成它的工作),那么 B 也应该被取消 - 因为从语义上讲,直到 A 完成它的工作才能开始。

那是一厢情愿...

文档说的是不同的。当您取消 B 时,尽管依赖于 A,但它不会等待 A 完成,然后再将其从队列中删除。如果 A 还没有完成 - 取消 B 将立即从队列中删除它 - 因为它仍然处于未决状态(A 的完成)。

Soooo.....为了完成我的计划,我需要引入我自己的“依赖”机制,可能是以一组布尔属性的形式,比如isPhotoTakenisPhotoProcessed,然后依赖于这些的操作,需要检查它的(执行块的)前导码是否所有必需的先前操作实际上已成功完成。

继承 NSBlockOperation 可能是值得的,如果任何“依赖项”已被取消,则覆盖调用“开始”以跳至完成的逻辑......但这是一个长期的目标,可能难以实现。

最后,我写了这个快速的子类,它似乎工作了——当然需要更深入的检查:

@interface MYBlockOperation : NSBlockOperation {
}
@end

@implementation MYBlockOperation
- (void)start {
    if ([[self valueForKeyPath:@"dependencies.@sum.cancelled"] intValue] > 0)
        [self cancel];
    [super start];
}
@end

当我在原始问题中将 NSBlockOperation 替换为 MYBlockOperation 时(以及我的其他测试,行为与我预期的一样。


推荐阅读