Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSOperationQueue waitUntilAllOperationsAreFinished not working while in background

The app I'm working on periodically refreshes it's local data cache from an application server (10+ requests, each of them take a fair amount of time). I'm currently running these requests asynchronously as to not block the UI thread. Since these requests do take a while to process and then load into core data, I'd like to leverage the beginBackgroundTaskWithExpirationHandler and dependent operation behavior of the NSOperationQueue.

After I've added all my requests to the operation queue, I'm using the waitUntilAllOperationsAreFinished to block until all the operations are finished (this isn't on the main thread). The problem I'm seeing in my prototype is that when I run the app and immediately background it (press the home button), the waitUntilAllOperationsAreFinished remains blocked even after all the operations have finished... but as soon as I open the app again, the handler finishes. If I run the app and let it remain in the foreground, everything finishes fine. This behavior doesn't always seem to happen when in my actual app, but with the example code below, it seems to:

#import "ViewController.h"

@interface ViewController ()

@property (assign, nonatomic) UIBackgroundTaskIdentifier task;
@property (strong, nonatomic) NSOperationQueue *queue;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self performSelectorInBackground:@selector(queueItUp) withObject:nil];
}

- (void)queueItUp {
    UIApplication *application = [UIApplication sharedApplication];

    self.queue = [[NSOperationQueue alloc] init];
    self.task = [application beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"Took too long!");

        [self.queue cancelAllOperations];
        [application endBackgroundTask:self.task];
        self.task = UIBackgroundTaskInvalid;
    }];

    for (int i = 0; i < 5; i++) {
        [self.queue addOperationWithBlock:^{
            [NSThread sleepForTimeInterval:3];
            NSLog(@"Finished operation.");
        }];
    }


    NSLog(@"Waiting until all operations are finished.");

    [self.queue waitUntilAllOperationsAreFinished];

    [application endBackgroundTask:self.task];
    self.task = UIBackgroundTaskInvalid;

    NSLog(@"All done :)");
}

@end

What am I doing wrong?

Thanks

like image 718
chinabuffet Avatar asked Mar 29 '13 13:03

chinabuffet


2 Answers

Your example code does not block perpetually at any point on iOS 6.1 on an iPhone or the simulator. Put a breakpoint at the line [application endBackgroundTask:self.task];, and you will hit it every time.

The behavior you are seeing is because you are logging while in the background after you've told the application to end the background task. I'm not sure of the exact details, but the logs are queued somehow to be printed to the console upon your application's restoration to the foreground. It may be the case that the thread execution is suspended instead.

If you move your NSLog(@"All done"); up to before your call to endBackgroundTask:, you will see the logged output.

like image 97
Carl Veazey Avatar answered Oct 20 '22 21:10

Carl Veazey


Using your sample code, the operations are completing just fine. It's on NSLog(@"All done :)"); that you are hanging. Your queue still exists and has no pending operations, but you may no longer have an active runloop and the main thread is blocked as you and in the background. Since you have completed your pending background operations, you are blocked. When you resume your application continues where it left off. If you do this:

    [[self queue] addOperationWithBlock:^{
        NSLog(@"All done :)");
    }];

The behavior should be even more obvious. It's doing exactly what you're telling it to do.

You have a bunch of operations queued up, and you have called waitUntilAllOperationsAreFinished. When they finish, you are blocked in the background.

It seems like you are trying to execute something when this group of operations are finished. NSOperation provides the ability to have dependancies and completion blocks that allow you to construct this kind of behavior. You can group operations and set a completion block or operation that runs when your group of operations completes. Some of this is covered in the Concurrency Programming Guide

like image 1
quellish Avatar answered Oct 20 '22 21:10

quellish