iOS背景执行指南示例的Apple编程指南?

时间:2016-01-22 16:38:27

标签: ios race-condition uibackgroundtask

所以在这个页面上有一个关于后台执行的例子:https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html#//apple_ref/doc/uid/TP40007072-CH4-SW1,这是一个例子:

- (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;
    });

}

据说bgTask在类中被定义为变量。因此,每个类(对象)实例都有一个bgTask属性。如果在异步块完成之前多次调用applicationDidEnterBackground,那么竞争条件是否存在危险?我的意思是bgTask会更改其值,endBackgroundTask会在新任务值上调用,而不是旧值吗?

这不是一个更好的解决方案:

__block UIBackgroundTaskIdentifier bgTask;

在致电beginBackgroundTaskWithName之前?

3 个答案:

答案 0 :(得分:2)

每个对象都有bgTask的一个实例,但它位于AppDelegate上,而不是一般的VC或对象。因此,从技术上讲,只有一个bgTask实例在工作。

但这仍然会产生问题。因为如果此方法被调用两次,它将覆盖bgTask的值。我的第一个想法是,在退出应用程序时,不止一次,以前的所有任务都将过期。但经过测试意识到情况并非如此(这是IMO的一件好事)。发生了什么事情是bgTask被覆盖(如预期的那样)并且新值被传递给第一个endBackgroundTask:调用。紧接其后bgTask设置为UIBackgroundTaskInvalid,将其清除,清除的值将传递给endBackgroundTask:的任何后续调用。这显然导致了一个不干净的终止,因为并非所有唯一的id都已经结束,导致expiration处理程序在任何剩余的后台任务上执行。

话虽如此,我相信你对使用局部变量的假设是正确的。如果您试用此代码(放在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;
    });
});

您会看到每个本地bgTask都被分配了一个唯一值,并在10秒后正确完成(根据dispatch_after来电)。

答案 1 :(得分:1)

我认为您要解决的问题如下:
应用程序将发送到Background状态,iOS会调用applicationDidEnterBackground: 启动后台任务,可能需要几分钟时间 在此期间,应用程序再次被激活,并再次发送到后台,再次调用applicationDidEnterBackground:,并启动另一个后台任务,如果变量bgTask不是块变量,则会覆盖变量bgTask。这是对的。因此,select studentInfo.Subject, avg(studentInfo.marks) from studentInfo group by studentInfo.Subject 确实应该是一个块变量 与此问题相关的问题是,如果已启动多个后台任务,您将如何完成后台执行。例如,如何执行此操作,将获得here 我们的想法是拥有一个计算活动后台任务的变量。一旦完成所有这些操作,就可以终止后台执行。

答案 2 :(得分:1)

你是对的,第二次调用时,applicationDidEnterBackground会导致问题。但是为了第二次调用该方法,首先需要将应用程序放入前台。所以解决方案很简单。只需从applicationWillEnterForeground

调用您的到期处理程序
- (void)expireBackgroundTask {
    // Clean up any unfinished task business by marking where you
    // stopped or ending the task outright.
    [application endBackgroundTask:bgTask];            
    bgTask = UIBackgroundTaskInvalid;
}

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

    bgTask = [application beginBackgroundTaskWithName:@"MyTask" expirationHandler:^{
        [self expireBackgroundTask];
    }];

    // 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;
    });

}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    [self expireBackgroundTask];
}