【问题标题】:Objective-C wait for asynchronous operation and enqueue completion handlersObjective-C 等待异步操作和排队完成处理程序
【发布时间】:2015-03-04 23:53:01
【问题描述】:

我必须计算一个昂贵的价值。计算完这个值后,我想运行一个完成处理程序块:

-(void) performCostlyCalculationWithCompletionHandler:(void (^)(void)complete 
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        id result = [self costlyCalculation];
        dispatch_async(dispatch_get_main_queue(), ^{
            complete(result);
        });
    });
}

相当标准。

现在,我希望能够重复调用此函数,而无需重新排队 costlyCalculation。如果costlyCalculation 已经在运行,我只想保存完成块并在costlyCalculation 完成后使用相同的result 调用它们。

有没有一种简单的方法可以用 GCD 或 NSOperationQueue 做到这一点?还是我应该将完成块存储在NSArray 中并自己调用它们?如果我这样做,我需要在这个数组周围放置什么样的同步?

更新

我可以接近dispatch_group_notify。基本上,我可以将工作块排入队列并将所有完成处理程序排入队列以在组之后运行:

dispatch_group_t group = dispatch_group_create();

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queue, ^(){
    // Do something that takes a while
    id result = [self costlyCalculation];
    dispatch_group_async(group, dispatch_get_main_queue(), ^(){
        self.result = result;
    });
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^(){
    complete(result);
});

这可行,但我如何判断costlyCalcuation 是否已经在运行,并且如果我不需要的话,不将工作排入队列?

【问题讨论】:

  • 您是否可以将结果保存为 ivar,然后在执行 costlyCalculation 之前测试 result 的存在,或者简单地使用 BOOL 标记函数的开始和结束,然后如果函数正在运行将块添加到数组中,如 q 中所述,并在计算完成后调用所有排队的块。如果你想要一些代码,我之前已经做过这两件事。
  • 不想事先检查result,因为在第一次运行时,多个请求会快速连续出现,这会导致对costlyCalculation的重复调用。也许用 BOOL 标记开始和结束是正确的方法。感谢您的建议。
  • 当然,我使用AVAssetImageGenerator 从资产生成缩略图。然后将缩略图绘制成屏幕上不同位置的几个CALayersAVAssetImageGenerator 运行时可见层可能会来来去去。
  • 我同意@Rob,但我还要提一下,您可以结合使用 BOOL 方法和 iVar 方法来防止多次调用 costlyCalculation,然后让结果保持不变(这将是如果结果始终相同,则最好每次都解决)。
  • 我打电话给generateCGImagesAsynchronouslyForTimes,生成的图像需要在屏幕上不同位置的几个不同层中重复使用。所以如果在generateCGImagesAsynchronouslyForTimes 运行时出现了一个新层,我想在这个新层中重复使用相同的图像而不重新运行图像生成器

标签: ios objective-c grand-central-dispatch nsoperationqueue


【解决方案1】:

我认为您已经基本解决了这个问题。我刚刚想出了一个使用 NSOperationQueue 和 NSOperations 之间的依赖关系的替代方案。这是我认为的伪代码。

// somewhere, create operation queue
NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];

-(void)tryCalculation:(CompletionBlockType)completionBlock
{
    if(opQueue.operationCount > 0)
    {
        NSOperation *op = [[NSOperation alloc] init];
        op.completionBlock = completionBlock;
        // you can control how to synchronize completion blocks by changing dependency object. In this example, all operation will be triggered at once when costly calculation finishes
        [op addDependency:[opQueue.operations firstObject]];
        [opQueue addOperation:op];
    }
    else
    {
        NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(costlyCalculation) object:nil];
        op.completionBlock = completionBlock;
        [opQueue addOperation:op];
    }
}

仍然可能存在微妙的时间问题。也许我们可以在 costlyCalculation 函数中使用额外的标志。

【讨论】:

  • 感谢您的代码。您认为什么时间问题?
  • @bcattle 从理论上讲,costlyCalculation 可以在第一个 if 块中创建 NSOperation 对象时完成。您创建了新的操作,认为有操作可以依赖,但创建后它就消失了。我不确定在这种情况下会发生什么:)
  • 此代码假定正在执行的任务本身是同步的。如果它是异步的,则必须将其包装为 asynchronous NSOperation subclass,或者更糟糕的是,通过使用信号量等使其行为同步。
  • @Rob NSOperation 添加到队列异步运行,这就是为什么我调用 addDependency 方法使其同步。或者,我是否从您的评论中遗漏了一些要点?
  • 我的意思是,如果您创建一个 NSBlockOperationNSInvocationOperation 来调用本身异步的东西(例如 generateCGImagesAsynchronouslyForTimes),即使启动任务仍在进行中,因此完全违背了依赖关系的目的。但是,异步NSOperation 子类可以以这样的方式实现,即操作不会发出“完成”的信号,直到您定义它(例如,您在异步部件时发布isFinished KVO的操作都完成了)。
猜你喜欢
  • 1970-01-01
  • 2022-01-21
  • 1970-01-01
  • 1970-01-01
  • 2017-02-15
  • 2023-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多