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的{​​{1}}状态为op块返回。队列仍然执行它们,就像YES成功完成一样。

然后我进一步挑战了文档,就像这样:

op

但是很害怕在日志中看到以下输出:

    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,但已执行。

有想法吗?

解决方法

如果您取消操作,则只需提示即可完成,特别是在长时间运行的任务中,您必须自己实现逻辑。 如果您取消某些操作,则依赖项将认为任务已完成并且没有问题。

因此,您需要做的是设置某种全局同步变量,并以同步方式获取该变量,该变量应捕获您的逻辑。您的运行操作应定期并在关键点检查该变量,然后退出。请不要使用实际的 global ,而是使用所有进程都可以访问的一些公共变量-我想您会很容易实现这一点?

取消不是停止操作运行的灵丹妙药,它只是调度程序的提示,可以使它优化内容。取消,您必须自己做。

这是解释,我可以给出它的示例实现,但是我认为您可以自己查看代码来做到这一点?

编辑

如果有许多依赖并且顺序执行的块,则您甚至不需要操作队列,或者只需要串行(一次1个操作)队列。如果这些块按顺序执行,但有很大不同,那么您需要按照条件失败后不添加新块的逻辑进行工作。

编辑2

关于我建议您如何解决此问题的一些想法。当然,细节很重要,但这也是一种不错的直接方法。这是一种伪代码,因此请不要在语法上迷路。

// Do it all in a class if possible,not subclass of NSOpQueue
class A

  // Members
  queue

  // job1
  synced state cancel1    // eg triggered by UI
  synced state counter1
  state calc1 that job 1 calculates (and job 2 needs)

  synced state cancel2
  synced state counter2
  state calc2 that job 2 calculated (and job 3 needs)
  ...

start
  start on queue

    schedule job1.1 on (any) queue
       periodically check cancel1 and exit
       update calc1
       when done or exit increase counter1

    schedule job1.2 on (any) queue
       same
    schedule job1.3
       same

  wait on counter1 to reach 0
  check cancel1 and exit early

  // When you get here nothing has been cancelled and
  // all you need for job2 is calculated and ready as
  // state1 in the class.
  // This is why state1 need not be synced as it is
  // (potentially) written by job1 and read by job2
  // so no concurrent access.

    schedule job2.1 on (any) queue

   and so on

对我来说,这是最直接的方法,可以为将来的开发做准备。易于维护和理解,等等。

编辑3

我喜欢并更喜欢这样做的原因是因为它可以将所有相互依赖的逻辑保持在一个地方,并且如果需要更好的控制,很容易在以后添加或校准它。

原因我喜欢这个,例如NSOp的子类化是,然后将这种逻辑散布到许多已经很复杂的子类中,并且还失去了一些控制权。在这里,只有在测试了某种条件并知道下一批需要运行之后,才安排工作。在替代方案中,您可以一次调度所有任务,并且需要在所有子类中使用其他逻辑来监视任务的进度或取消的状态,以便迅速发挥作用。

对NSOp进行子类化如果在该子类中运行的特定操作需要校准,我会这样做,但是将其子类化以管理相互依赖性会增加我的复杂性。

(可能是最终的)EDIT 4

如果到目前为止,您印象深刻。现在,查看我建议的(伪)代码段,您可能会发现它过于矫and过正,并且可以大大简化它。这是因为呈现方式不同,整个任务(任务1,任务2等)的不同组成部分似乎是断开的。如果是这样,您确实可以通过多种不同且更简单的方式来执行此操作。在参考文献中,如果所有任务都相同或非常相似,或者每个子任务(例如1)只有一个子任务(例如1.1),或者正在运行一个(子或子子)任务,则我提供了一种不错的方法在任何时间点。

但是,对于真正的问题,您可能最终会在这些问题之间得到干净而线性的流动。换句话说,在任务2说完之后,您可以踢出任务3.1,这不是任务4或5所必需的,而只是任务6所需要的。然后,取消和退出早期逻辑已经变得很棘手,而我没有将其分解的原因分解成更小和更简单的位真的是因为像这里一样,逻辑也可以(轻松地)跨越这些子任务,并且因为这个class A代表了更大的整体,例如清除数据或拍照,或者尝试解决的最大问题是什么。

此外,如果您处理的工作确实很慢,并且需要降低性能,则可以通过找出(子和子子)任务之间的依赖关系并尽快启动它们来实现。这种类型的校准是在UI上花费了很长时间解决的(现实)问题变得可行的地方,因为您可以分解它们并(以非线性方式)将它们组合在一起,从而可以以最有效的方式遍历它们

我遇到了一些这样的问题,尤其是我想知道它变得非常脆弱,很难遵循逻辑,但是通过这种方式,我能够将解决方案的时间从不可接受的一分钟多缩短了只需几秒钟,并得到用户的同意。

(这次实际上几乎是决赛)编辑5

此外,当您在解决问题上取得进展时,此处显示的方式是在任务1和2或任务2和3之间的关键时刻,您可以在其中使用进度和部件更新UI完整解决方案从所有各种(子和子子)任务中汲取的力量。

(即将结束)编辑6

如果您在单个内核上工作,那么除了任务之间的相互依赖性之外,安排所有这些子任务和子子任务的顺序都没有关系,因为执行是线性的。一旦拥有多个内核,就需要将解决方案分解为尽可能小的子任务,并尽快安排运行时间较长的子任务以提高性能。您获得的性能挤压可能会很大,但代价是所有小小的子任务之间的流程越来越复杂,并且处理取消逻辑的方式也变得如此。

,

好。我想我解决了这个谜。我只是误解了[NSOperation cancel] documentation

它说:

在macOS 10.6和更高版本中,如果操作在队列中但正在等待 未完成的从属操作,这些操作随后 忽略了。由于已经取消,因此此行为允许 操作队列,以更快,更清晰地调用操作的启动方法 该对象不在队列中。如果取消不参与的操作 在队列中,此方法立即将对象标记为完成。每一个 情况下,将对象标记为就绪或完成会导致 生成适当的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时,行为与我预期的一样。

相关问答

错误1:Request method ‘DELETE‘ not supported 错误还原:...
错误1:启动docker镜像时报错:Error response from daemon:...
错误1:private field ‘xxx‘ is never assigned 按Alt...
报错如下,通过源不能下载,最后警告pip需升级版本 Requirem...