Dispatch_group_wait with GCD

So, I am sending an array of images to my server. I want to use a GCD to host an asynchronous array, but I also want to make a method in which this happens synchronously so that I can pass one response object. However, the dispatch_group_wait method seems to return immediately (and did not wait for my blocks to complete). Is this a problem because I use a block inside a block?

NSArray *keys = [images allKeys];
__block NSMutableDictionary *responses = [NSMutableDictionary dictionaryWithCapacity:[images count]];

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < [keys count]; i++) {
    __block NSString *key = [keys objectAtIndex:i];
    dispatch_group_async(group, queue, ^{
        [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
            @synchronized(responses){
                if ([response succeeded]) {
                    NSString *value = [[response data] objectForKey:@"image_token"];
                    [responses setObject:value forKey:key];
                    NSLog(@"inside success %@",responses);
                } else {
                    NSString *error = response.displayableError;
                    if (!error) {
                        error = @"Sorry something went wrong, please try again later.";
                    }
                    [responses setObject:error forKey:@"error"];
                    [responses setObject:response forKey:@"response"];
                }
            }
        }];
    });
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);

I just want to wait until all the [self postImage] methods access the server and change the response dictionary.

+5
source share
3 answers

. , , , , , . , CV , N .

, ( ), , , .

NSUInteger numKeys = [keys count];

NSConditionLock *conditionLock = [[NSConditionLock alloc] initWithCondition:numKeys];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSUInteger i = 0; i < numKeys; i++) {
    __block NSString *key = [keys objectAtIndex:i];
    dispatch_async(queue, ^{
        [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
            // Basically, nothing more than a obtaining a lock
            // Use this as your synchronization primitive to serialize access
            // to the condition variable and also can double as primitive to replace
            // @synchronize -- if you feel that is still necessary
            [conditionLock lock];

            ...;

            // When unlocking, decrement the condition counter
            [conditionLock unlockWithCondition:[conditionLock condition]-1];
        }];
    });
}

// This call will get the lock when the condition variable is equal to 0
[conditionLock lockWhenCondition:0];
// You have mutex access to the shared stuff... but you are the only one
// running, so can just immediately release...
[conditionLock unlock];
+9

-postImage:completionHandler:, , , , -, iOS. , , , iOS, . , .

, , dispatch_group_wait(). , - , , , , () .

NSUInteger numKeys = [keys count];

dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSUInteger i = 0; i < numKeys; i++) {
    __block NSString *key = [keys objectAtIndex:i];
    dispatch_group_async(group, queue, ^{
        // We create a semaphore for each block here. More on that in a moment.
        // The initial count of the semaphore is 1, meaning that a signal must happen
        // before a wait will return.
        dispatch_semaphore_t sem = dispatch_semaphore_create(1);
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
        [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
            ...
            // This instructs one caller (i.e. the outer block) waiting on this semaphore to resume.
            dispatch_semaphore_signal(sem);
        }];

        // This instructs the block to wait until signalled (i.e. at the end of the inner block.)
        dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);

        // Done with the semaphore. Nothing special here.
        dispatch_release(sem);
    });
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
// Now all the tasks should have completed.
dispatch_release(group);

. - . , 100 , ​​ 99 ? ™ . , , . , . !

NSUInteger numKeys = [keys count];

// set the count of the semaphore to the number of times it must be signalled before
// being exhausted. Up to `numKeys` waits will actually wait for signals this way.
// Additional waits will return immediately.
dispatch_semaphore_t sem = dispatch_semaphore_create(numKeys);
for (int i = 0; i < numKeys; i++) {
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (NSUInteger i = 0; i < numKeys; i++) {
    __block NSString *key = [keys objectAtIndex:i];
    dispatch_async(queue, ^{
        [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
            ...;

            // This decrements the semaphore count by one. The calling code will be
            // woken up by this, and will then wait again until no blocks remain to wait for.
            dispatch_semaphore_signal(sem);
        }];
    });
}

// At this point, all the work is running (or could have already completed, who knows?).
// We don't want this function to continue running until we know all of the blocks
// have run, so we wait on our semaphore a number of times equalling the number
// of signals we expect to get. If all the blocks have run to completion before we've
// waited for all of them, the additional waits will return immediately.
for (int i = 0; i < numKeys; i++) {
    dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
}
// Now all the tasks should have completed.
dispatch_release(sem);
+7

, , dispatch_group_wait() , postImage:completionHandler: . , , -, .

, , , , GCD . ; -, , , .

The essence of the solution is to significantly transfer ownership of the dictionary to be changed by one queue, and then only change it from this queue. The “ownership” to which I refer is not ownership of objects in the sense of memory management, but ownership of a change in meaning.

I would think of doing something like this:

NSArray *keys = [images allKeys];
// We will not be reasigning the 'responses' pointer just sending messages to the NSMutableDictionary object __block is not needed.
NSMutableDictionary *responses = [NSMutableDictionary dictionaryWithCapacity:[images count]];
// Create a queue to handle messages to the responses dictionary since mutables are not thread safe.
// Consider this thread the owner of the dictionary.
dispatch_queue_t responsesCallbackQueue = dispatch_queue_create("com.mydomain.queue", DISPATCH_QUEUE_SERIAL);

for (NSString *key in keys) {
    // This async call may not be needed since postImage:completionHandler: seems to be an async call itself.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
        [self postImage:[images objectForKey:key] completionHandler:^(ServerResponse *response){
            dispatch_async(responsesCallbackQueue, ^{
                [responses setObject:response forKey:key];
                // Perhaps log success for individual requests.
                if (responses.count == keys.count){
                    NSLog(@"All requests have completed");
                    // All responses are available to you here.
                    // You can now either transfer back 'ownership' of the dictionary.
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [self requestsHaveFinished:responses];
                    });
                }
            });
        }];
    });
}
+3
source

All Articles