0

我正在使用 NSOperationQueue 来排队和调用一些地理编码位置查找。我想在所有异步运行的查找完成后调用完成方法。

-(void)geocodeAllItems {

    NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc]init];
    [geoCodeQueue setName:@"Geocode Queue"];

    for (EventItem *item in [[EventItemStore sharedStore] allItems]) {
        if (item.eventLocationCLLocation){
            NSLog(@"-Location Saved already. Skipping-");
            continue;
        }

        [geoCodeQueue addOperationWithBlock:^{

            NSLog(@"-Geocode Item-");
            CLGeocoder* geocoder = [[CLGeocoder alloc] init];
            [self geocodeItem:item withGeocoder:geocoder];

        }];
    }

    [geoCodeQueue addOperationWithBlock:^{
        [[NSOperationQueue mainQueue]addOperationWithBlock:^{
            NSLog(@"-End Of Queue Reached!-");
        }];
    }];


}

- (void)geocodeItem:(EventItem *)item withGeocoder:(CLGeocoder *)thisGeocoder{

    NSLog(@"-Called Geocode Item-");
    [thisGeocoder geocodeAddressString:item.eventLocationGeoQuery completionHandler:^(NSArray *placemarks, NSError *error) {
        if (error) {
            NSLog(@"Error: geocoding failed for item %@: %@", item, error);
        } else {

            if (placemarks.count == 0) {
                NSLog(@"Error: geocoding found no placemarks for item %@", item);
            } else {
                if (placemarks.count > 1) {
                    NSLog(@"warning: geocoding found %u placemarks for item %@: using the first",placemarks.count,item);
                }
                NSLog(@"-Found Location. Save it-");
                CLPlacemark* placemark = placemarks[0];
                item.eventLocationCLLocation = placemark.location;
                [[EventItemStore sharedStore] saveItems];
            }
        }
    }];
}

输出

[6880:540b] -Geocode Item-
[6880:110b] -Geocode Item-
[6880:540b] -Called Geocode Item-
[6880:110b] -Called Geocode Item-
[6880:110b] -Geocode Item-
[6880:540b] -Geocode Item-
[6880:110b] -Called Geocode Item-
[6880:540b] -Called Geocode Item-
[6880:110b] -Geocode Item-
[6880:580b] -Geocode Item-
[6880:1603] -Geocode Item-
[6880:110b] -Called Geocode Item-
[6880:1603] -Called Geocode Item-
[6880:580b] -Called Geocode Item-
[6880:907] -End Of Queue Reached!-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-
[6880:907] -Found Location. Save it-

如您所见,End of Queue 函数在所有地理编码过程 + 保存事件的实际结束之前被调用。只有在处理完所有排队查找时,才应在最后显示“已到达队列结束”。我怎样才能把它变成正确的顺序?

4

5 回答 5

7

这里出现了几个问题。一方面,geocodeAddressString:它是异步的,所以它立即返回并且块操作正在结束,允许下一个立即开始。其次,您不应该geocodeAddressString:一个接一个地拨打多个电话。来自 Apple 的这种方法的文档:

After initiating a forward-geocoding request, do not attempt to 
initiate another forward-or reverse-geocoding request.

第三,您没有在 NSOperationQueue 上设置最大并发操作数,因此多个块可能会同时执行。

由于所有这些原因,您可能希望使用一些 GCD 工具来跟踪您对geocodeAddressString:. 您可以使用 dispatch_semaphore (以确保一个在另一个开始之前完成)和一个 dispatch_group (以确保您知道它们何时都完成)来做到这一点 - 类似于以下内容。假设您已经声明了这些属性:

@property (nonatomic, strong) NSOperationQueue * geocodeQueue;
@property (nonatomic, strong) dispatch_group_t geocodeDispatchGroup;
@property (nonatomic, strong) dispatch_semaphore_t geocodingLock;

并像这样初始化它们:

self.geocodeQueue = [[NSOperationQueue alloc] init];
[self.geocodeQueue setMaxConcurrentOperationCount: 1];
self.geocodeDispatchGroup = dispatch_group_create();
self.geocodingLock = dispatch_semaphore_create(1);

您可以像这样进行地理编码循环(我对代码进行了一些更改以使关键部分更加明显):

-(void) geocodeAllItems: (id) sender
{
    for (NSString * addr in @[ @"XXX Address 1 XXX", @"XXX Address 2 XXX", @"XXX Address 3 XXXX"]) {
        dispatch_group_enter(self.geocodeDispatchGroup);
        [self.geocodeQueue addOperationWithBlock:^{
            NSLog(@"-Geocode Item-");
            dispatch_semaphore_wait(self.geocodingLock, DISPATCH_TIME_FOREVER);
            [self geocodeItem: addr withGeocoder: self.geocoder];
        }];
    }
    dispatch_group_notify(self.geocodeDispatchGroup, dispatch_get_main_queue(), ^{
        NSLog(@"- Geocoding done --");
    });
}

- (void)geocodeItem:(NSString *) address withGeocoder:(CLGeocoder *)thisGeocoder{

    NSLog(@"-Called Geocode Item-");
    [thisGeocoder geocodeAddressString: address completionHandler:^(NSArray *placemarks, NSError *error) {
        if (error) {
            NSLog(@"Error: geocoding failed for item %@: %@", address, error);
        } else {
            if (placemarks.count == 0) {
                NSLog(@"Error: geocoding found no placemarks for item %@", address);
            } else {
                if (placemarks.count > 1) {
                    NSLog(@"warning: geocoding found %u placemarks for item %@: using the first",placemarks.count, address);
                }
                NSLog(@"-Found Location. Save it:");
            }
        }
        dispatch_group_leave(self.geocodeDispatchGroup);
        dispatch_semaphore_signal(self.geocodingLock);
    }];
}
于 2013-01-02T20:06:14.103 回答
4

一个好的解决方案是将所有地理编码操作添加为最终清理操作的依赖项:

- (void)geocodeAllItems {
    NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc] init];

    NSOperation *finishOperation = [NSBlockOperation blockOperationWithBlock:^{
        // ...
    }];

    for (EventItem *item in [[EventItemStore sharedStore] allItems]) {
        // ...
        NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
            // ...
        }];
        [finishOperation addDependency:operation]
        [geoCodeQueue addOperation:operation];
    }

    [geoCodeQueue addOperation:finishOperation];
}

另一种解决方案是使操作队列串行。这些操作仍然在后台线程上执行,但一次只执行一个,并且按照它们添加到队列中的顺序:

NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc] init];
[geoCodeQueue setMaxConcurrentOperationCount:1];
于 2013-01-02T17:40:37.030 回答
3

CompletionBlocks 内置在 NSOperation 中,并且 NSBlockOperation 可以处理多个块,因此只需添加运行异步所需的所有工作并设置完成块以在全部完成时调用,这真的很容易。

- (void)geocodeAllItems {
    NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc] init];

    NSBlockOperation *operation = [[[NSBlockOperation alloc] init] autorelease]

    for (EventItem *item in [[EventItemStore sharedStore] allItems]) {
        // ...
        // NSBlockOperation can handle multiple execution blocks
        operation addExecutionBlock:^{
            // ... item ...
        }];
    }

    operation addCompletionBlock:^{
         // completion code goes here
         // make sure it notifies the main thread if need be.
    }];

    // drop the whole NSBlockOperation you just created onto your queue
    [geoCodeQueue addOperation:operation];
}

注意:您不能假设这些操作将在您的 geoCodeQueue 中执行。它们将同时运行。NSBlockOperation 管理这个并发。

于 2013-01-02T18:49:26.283 回答
1

NSOperationQueue 并不像你想象的那样工作,执行顺序和添加顺序之间没有直接的依赖关系。你可以调用你减去的函数,直到数字等于零,你可以调用“回调”函数。

于 2013-01-02T17:37:14.973 回答
1

NSOperationQueues 默认同时运行多个操作。当然,在实践中,这意味着添加到队列中的操作不一定会按照您添加它们的顺序开始或完成。

您可以通过在创建队列后将队列的 maxConcurrentOperationCount 值设置为 1 来使队列连续运行所有操作:

NSOperationQueue *geoCodeQueue = [[NSOperationQueue alloc]init];
[geoCodeQueue setName:@"Geocode Queue"];
[geoCodeQueue setMaxConcurrentOperationCount:1];

如果您确实希望操作同时运行,但仍希望在它们全部完成时收到通知,请观察队列的operations属性并等到它达到零,正如 Srikanth 在他的评论中链接的答案中所解释的那样。

编辑:Nikolai Ruhe 的回答也很棒。

于 2013-01-02T17:42:19.217 回答