Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

iOS: Don't return from function until multiple background threads complete

This seems like it should be really simple but I'm having a lot of trouble. I have a function that fires off a bunch of other functions that run in the background and have completion blocks. I want my function to wait until all of the completion blocks have been called before it returns.

I do not have control of the function I am calling that executes in the background. Otherwise I would just modify it to use dispatch_async with my own queue and then wait on that queue to finish.

Example of what my situation looks like:

- (void)functionThatShouldBeSynchronous {
    for (int i = 0; i < 10; i++) {
        [self doSomethingInBackground:^{
            NSLog(@"completed!");
        }];
    }
    // How do I wait until all 10 threads have completed before returning?
}

- (void)doSomethingInBackground:(void(^)())completion {
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        [NSThread sleepForTimeInterval:1.0f]; // Do stuff
        completion(); // Execute completion block
    });
}

Thanks in advance.

like image 347
Kevin Craft Avatar asked May 12 '13 22:05

Kevin Craft


1 Answers

Use a dispatch group like this:

- (void)functionThatShouldBeSynchronous {
    dispatch_group_t taskGroup = dispatch_group_create();

    for (int i = 0; i < 10; i++) {
        dispatch_group_enter(taskGroup);
        [self doSomethingInBackground:^{
            NSLog(@"completed!");
            dispatch_group_leave(taskGroup);
        }];
    }

    // Waiting for threads
    dispatch_group_wait(taskGroup, DISPATCH_TIME_FOREVER);
    dispatch_release(taskGroup);

    // Background work complete
}

If you want a timeout waiting for the threads you could change the dispatch_group_wait line to this

// Waiting 10 seconds before giving up
if (dispatch_group_wait(taskGroup, dispatch_time(DISPATCH_TIME_NOW, 10000000000)) != 0) {
    // Timeout
}

The parameter is in nanoseconds.

As bbum were saying, you shouldn't block the main thread. In that case you could do it like this:

typedef void(^MyCompletionHandler)();
-(void)functionDoingBackgroundWorkWithCompletionHandler:(MyCompletionHandler)completionHandler {
    dispatch_group_t taskGroup = dispatch_group_create();

    for (int i = 0; i < 10; i++) {
        dispatch_group_enter(taskGroup);
        [self doSomethingInBackground:^{
            NSLog(@"completed!");
            dispatch_group_leave(taskGroup);
        }];
    }

    dispatch_queue_t waitingQueue = dispatch_queue_create("com.mycompany.myapp.waitingQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(waitingQueue, ^{
        // Waiting for threads
        dispatch_group_wait(taskGroup, DISPATCH_TIME_FOREVER);
        dispatch_release(taskGroup);


        // Background work complete
        dispatch_async(dispatch_get_main_queue(), ^{
            // Calling the completion handler on the main thread (If you like)
            completionHandler();
        });
        dispatch_release(waitingQueue);
    });
}
like image 96
Vegard Avatar answered Sep 19 '22 14:09

Vegard