【问题标题】:NSOperation dependency and completionBlockNSOperation 依赖和完成块
【发布时间】:2023-04-08 01:17:01
【问题描述】:

我们遇到了一个关于 NSOperationQueue 的简单问题,下面是一个简单的操作逻辑:

self.queue = [[NSOperationQueue alloc] init];

NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"- Running operation A");
    [NSThread sleepForTimeInterval:1.2];
    NSLog(@"- Done operation A");
}];

NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"- Running operation B");
    [NSThread sleepForTimeInterval:2];
    NSLog(@"- Done operation B");
}];

[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A");
}];

[operationB setCompletionBlock:^{
    NSLog(@"-- Completion Block B");
}];

[operationB addDependency:operationA];
[self.queue addOperations:@[operationA, operationB] waitUntilFinished:NO];

这是最终输出

2015-12-21 14:59:57.463 SampleProject[18046:310901] - Running operation A
2015-12-21 14:59:58.664 SampleProject[18046:310901] - Done operation A
2015-12-21 14:59:58.664 SampleProject[18046:310900] - Running operation B
2015-12-21 14:59:58.664 SampleProject[18046:310904] -- Completion Block A
2015-12-21 15:00:00.736 SampleProject[18046:310900] - Done operation B
2015-12-21 15:00:00.736 SampleProject[18046:310904] -- Completion Block B

我们可以看到,操作B操作A的completionBlock之前执行。在我们的实际应用中,我们有很多操作A,只有一个操作B依赖于所有的操作A。但是我们遇到的问题是 operationB 在最后一个 operation A 的完成块被调用之前启动,这通常会向 operation B 提供信息

我如何让操作B在所有操作A的完成块之后执行?

【问题讨论】:

  • 可以选择切换到 GCD 吗?该 API 拥有您需要的所有工具。对于这种特殊情况,障碍块是您正在寻找的东西。有了这些,您可以并行运行许多 A 类任务,然后添加 B 类屏障任务,该任务在所有 A 完成之前无法启动,并且在 B 完成之前无法启动新任务。
  • 你能给我一个简短的例子来说明它是如何工作的吗?我对 GCD 有点了解,但对这类任务还不够
  • 老实说,我也不是专家,但前几天我读了 Ray Wenderlich 的tutorial,在第 1 部分“处理读者和作家问题”的中间,讨论了一些听起来很准确的东西喜欢你正在寻找的东西。
  • 使用 GCD 可以做任何事情,就像编写汇编一样可以做任何事情。 NSOperationQueue 建立在 GCD 之上。只是实施起来要困难得多。尝试取消 dispatch_queues 上的依赖块。 NSOperations 免费!尝试使用 GCD API 更改操作优先级,而无需费力。屏障实际上会停止执行块的线程,而 NSOperation 使用 KVC/KVO 更轻松地执行此操作 - 通过不“准备好”执行 - 直到您依赖的操作“完成”。 dispatch_queues 继续运行而没有“障碍”。 NSOperations 摇滚!

标签: objective-c nsoperation nsoperationqueue


【解决方案1】:

正如您在测试中发现的,完成块不是“队列的一部分”,而是在操作队列之外运行(以及在另一个线程上)。因此,操作 A 的完成块将与操作 B 同时运行(ish)。

我建议您重构代码以删除所有完成块。

您说您正在使用 completionBlocks 将信息从操作 A 传递给 B,对此有两种选择: 为 B 提供对所有 A 的引用(不弱),这样当 B 运行时,它可以从所有 A 中挑选结果.或者,如果由于某种原因在 B 运行之前保留所有 A 是不可行的,那么将您的 completionBlock 重新创建为另一个 NSOperation:

NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
    // do stuff
}];

NSOperation *operationATail = [NSBlockOperation blockOperationWithBlock:^{
    // do completionBlock stuff
}];

[operationATail addDependency:operationA];
[operationB addDependency:operationATail];
[self.queue addOperations:@[operationA, operationATail, operationB] waitUntilFinished:NO];

【讨论】:

  • 描述得很好。这正是我最终要做的(事实上,我在那些已经存在的操作之上创建了自定义 NSOperation 子类)。不得不说这样干净多了。我忘了在我的回答中解释,所以谢谢你解释它。
  • 为什么需要继承 NSOperation?通过引入您绝对知道的依赖项,可以确保在其他操作开始之前所有相关操作都已成功完成。因此,他们可以简单地共享一个数据对象来“传递所需的信息”,而后面的操作可以假设前者已经给它留下了所需的信息。您不需要在这里进行子类化,也不需要任何“触发器”——至少对于所描述的问题来说不是。
【解决方案2】:

为什么你不能调用完成块a内部的操作,这就是完成块的作用。

[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A");
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"- Running operation B");
        [NSThread sleepForTimeInterval:2];
        NSLog(@"- Done operation B");
    }];
    [queue addOperations:@[operationB] waitUntilFinished:NO];
}];


[operationA setCompletionBlock:^{
    NSLog(@"-- Completion Block A when we dont need B");
}];

有一些更好的方法来代替使用

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     [self operationB];
}

【讨论】:

  • 因为如前所述,我可能有多个操作A,而只有一个操作B。我不希望每个操作A 有1 个操作B,这有意义吗?
  • 那么为什么不在需要的时候设置不同的完成块呢。所以每次你需要在操作a完成后运行操作b,然后设置操作a的完成块以满足你的需要。
【解决方案3】:
  1. 避免完成块 - 它们是不适合的队列外机制 用于同步任何操作或其通信。

  2. 引入依赖(B 依赖于 A)意味着 B 只会在 A 成功完成后运行。

  3. 出于这个原因 - 任何简单的数据对象都可以用于在这两个操作之间安全地“传递信息” - 这可以简单地共享它(在定义两个操作的块之外创建它就足够了。当“B”运行时,它可以假设“A”已经在数据对象中放入了所需的信息,并简单地访问它。

    self.queue = [[NSOperationQueue alloc] init];
    
    NSMutableDictionary *info = [NSMutableDictionary new]; // data object for communication
    
    NSOperation *operationA = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"- Running operation A");
     [NSThread sleepForTimeInterval:1.2];
    
      info[@"DoThis"] = @YES;
      info[@"DoThat"] = @NO;
      NSLog(@"- Done operation A");
    }];
    
    NSOperation *operationB = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"- Running operation B");
    
      if ([info[@"DoThis"] boolValue] NSLog(@"Op A said to do this.");
      if ([info[@"DoThat"] boolValue] NSLog(@"Op A said to do that.");
    
      [NSThread sleepForTimeInterval:2];
      NSLog(@"- Done operation B");
    }];
    
    [operationB addDependency:operationA];
    [self.queue addOperations:@[operationA, operationB] waitUntilFinished:NO];
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-02-24
    相关资源
    最近更新 更多