iosrace-conditionuibackgroundtask

Apple programming guide for iOS background execution guide example?


So on this page there is an example about background execution: https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html#//apple_ref/doc/uid/TP40007072-CH4-SW1, here is the example:

- (void)applicationDidEnterBackground:(UIApplication *)application {

    bgTask = [application beginBackgroundTaskWithName:@"MyTask" expirationHandler:^{

        // Clean up any unfinished task business by marking where you
        // stopped or ending the task outright.
        [application endBackgroundTask:bgTask];

        bgTask = UIBackgroundTaskInvalid;
    }];

    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // Do the work associated with the task, preferably in chunks.

        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    });

}

It is said that bgTask is defined in class as variable. So there is one bgTask property for every instance of class (object). If applicationDidEnterBackground were to be called multiple times before async block finishes, isn't here danger of race condition? I mean bgTask would change its value, and endBackgroundTask would be called on new task value, instead of old value?

Isn't here a better solution to do this:

__block UIBackgroundTaskIdentifier bgTask;

before calling beginBackgroundTaskWithName?


Solution

  • There is one instance of of bgTask for each object, but this is on the AppDelegate, not some general VC or object. So there will technically only ever be one bgTask instance at work.

    But this still creates problems. Since if this method gets called twice it will override bgTask's value. My first thought was that upon exiting the app, more than once, all previous tasks would expire. But after testing realized this was not the case (which is a good thing IMO). What did happen is that bgTask was overwritten (as expected) and the new value was passed to the first endBackgroundTask: call. Immediate afterward bgTask is set to UIBackgroundTaskInvalid which clears it out and the cleared value is passed to any subsequent calls to endBackgroundTask:. This obviously resulted in an unclean termination since not all the unique id's would have been ended, leading to the expiration handler executing on any left over background tasks.

    That being said, I believe your assumption about using a local variable is correct. If you try out this code (placed in the AppDelegate applicationDidEnterBackground:):

    __block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithName:@"MyTask" expirationHandler:^{
        // Clean up any unfinished task business by marking where you
        // stopped or ending the task outright.
        NSLog(@"Expired");
        [application endBackgroundTask:bgTask];
        bgTask = UIBackgroundTaskInvalid;
    }];
    
    NSLog(@"Backgrounded: %@", @(bgTask));
    
    // Start the long-running task and return immediately.
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
        // Do the work associated with the task, preferably in chunks.
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"Done! %@", @(bgTask));
            [application endBackgroundTask:bgTask];
            bgTask = UIBackgroundTaskInvalid;
        });
    });
    

    You will see that each local bgTask is assigned a unique value and properly completed after 10 seconds (as per the dispatch_after call).