等待多个异步调用完成回调

4
我希望能够与网络服务同步一些数据。对于每个项目,我必须进行异步调用。
我想要一个完成块,在每个项目同步完成时被调用。对于每个项目,我都可以执行一个完成块。现在,我不知道如何做到这一点。
这是接口:
-(void) synchronizeItemsOnComplete:(CompleteBlock) block {
    NSArray* items = // get items
    for (int i = 0, n = [items count]; i < n; i++) {
       [self synchronizeItem:[items objectAtIndex:i] onComplete:^{
          // What do do here?
       }];
    }
    // And/or here?
}

-(void) synchronizeItemOnComplete:(CompleteBlock) block {
    // do something
    block();
}

我该如何等待同步完成然后执行代码块?

我尝试了以下代码:

NSArray* items = // get items

__block int countOfItemsUntilDone = [items count];

for (int i = 0, n = countOfItemsUntilDone; i < n; i++) {
    [self synchronizeItem:[items objectAtIndex:i] onComplete:^{
        countOfItemsUntilDone--;
    }];
}

dispatch_queue_t queue = dispatch_queue_create("wait for syncing", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    while (countOfItemsUntilDone > 0) {
        usleep(1000); // wait a little bit
    }
    block();
});
dispatch_release(queue);

但我认为这是一种相当糟糕的方式。有什么好的想法吗?
3个回答

4

不要在循环中等待计数器归零,每次递减计数器时检查其值,当计数器达到零时触发事件。

 -(void) synchronizeItemsOnComplete:(CompleteBlock) block {
    NSArray* items = // get items
    __block NSUInteger remaining = [items count];

    for (ItemClass* item in items) {
       [self synchronizeItemImage:item onComplete:^{
            --remaining;
            if (remaining == 0) {
                block();
            }
       }];
    }
 }

为了解释为什么感觉不对,这里有两个你正在做的事情,你应该从来不做或很少做:
  • 使用后台队列。这很困难且容易出错。在没有充分阅读有关编写并发代码的材料之前,请勿这样做。除非有操作需要阻塞相当长的时间(例如从磁盘读取文件或执行密集计算),否则您实际上只需要这样做。除非有很好的理由(例如可测量的性能问题),否则不要假设您需要这样做。
  • 在循环中旋转,检查变量是否更改并调用sleep。您绝不能这样做。
此外,如果您正在循环遍历数组中的元素,则for ... in语法比调用每个索引上的objectAtIndex:更好(并且可能更有效)。

2
不要在不同的线程中检查或减少共享内存,否则会导致竞态条件。使用调度组来完成你所要做的事情。
dispatch_queue_t myBGQueue;
dispatch_group_t itemsGroup = dispatch_group_create();

for (ItemClass *item in items) {
    dispatch_group_async(itemsGroup, myBGQueue, ^{
        [self synchronizeItemImage:item];
    });
}

/* execution will sleep here until all the blocks added in the `for` complete */
dispatch_group_wait(itemsGroup, DISPATCH_TIME_FOREVER);

dispatch_release(itemsGroup);

哦...我没有表明那一点很清楚。那个被称为synchronizeItemImage方法通过RESTKit发起异步请求。因此,尝试以异步或waitUntilDone:的方式执行该方法可能行不通。为了避免在不同线程中减少共享内存,我可以在串行调度队列中执行Chris Devereux的解决方案中的块,但这也是不好的吗? - Obenland
哦,那样的话,只要完成处理程序在主线程上调用,你就可以简单地减少一些变量。或者更好的方法是,在你的windowController或应用程序委托上减少一个属性,并使用KVO通知来找出何时它达到零。 - iluvcapra

1

您可以使用它们同步使用。

GCDthis

performSelector:waitUntilDone:YES


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接