4

所以在这个页面上有一个关于后台执行的例子: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

4

3 回答 3

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调用)。

于 2016-01-28T19:46:31.843 回答
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];
}
于 2016-01-30T16:55:19.303 回答
1

我认为您解决的问题如下:
一个应用程序被发送到Background状态,并且 iOS 调用applicationDidEnterBackground:.
后台任务已启动,这可能需要几分钟时间。
在此期间,应用程序再次被激活,并再次发送到后台,再次调用applicationDidEnterBackground:,并启动另一个后台任务,bgTask如果变量不是块变量,则该变量将被覆盖。这是对的。因此,bgTask确实应该是一个块变量。
与此问题相关的问题是,如果已启动多个后台任务,您如何才能完成后台执行。此处给出了如何执行此操作的示例。
这个想法是有一个变量来计算活动的后台任务。一旦它们全部完成,就可以终止后台执行。

于 2016-01-25T13:12:27.993 回答