beginBackgroundTaskWithExpirationHandler 调用 endBackgroundTask 但未结束进程

Posted

技术标签:

【中文标题】beginBackgroundTaskWithExpirationHandler 调用 endBackgroundTask 但未结束进程【英文标题】:beginBackgroundTaskWithExpirationHandler calling endBackgroundTask but not ending process 【发布时间】:2015-04-28 12:29:49 【问题描述】:

即使应用程序进入后台,我也想运行一些长时间运行的进程。我正在调用应用程序的beginBackgroundTaskWithExpirationHandler: 方法,在expirationBlock 中我正在调用应用程序的endBackgroundTask。 这是实现:

__block UIBackgroundTaskIdentifier task = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
    [[UIApplication sharedApplication] endBackgroundTask:task];
    task = UIBackgroundTaskInvalid;
];
dispatch_queue_t queue = dispatch_queue_create("com.test.test1234", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^
    // My Task goes here
);

在某些情况下,我的串行队列有更多任务要执行,这些任务无法在系统提供的时间内完成。所以到期块将执行,并且我正在结束 UIBackgroundTaskIdentifier 但没有停止调度过程(我什至不能取消调度)。

苹果的文件说:

对 beginBackgroundTaskWithName:expirationHandler: 或 beginBackgroundTaskWithExpirationHandler: 方法的每次调用都会生成一个唯一的令牌以与相应的任务相关联。当你的应用完成一个任务时,它必须调用 endBackgroundTask: 方法并带有相应的令牌,让系统知道该任务已经完成。未能为后台任务调用 endBackgroundTask: 方法将导致您的应用程序终止。如果您在启动任务时提供了过期处理程序,系统会调用该处理程序并给您最后一次机会来结束任务并避免终止。

因此,如果我不调用endBackgroundTask:,我的应用程序将被终止,这没关系。

我的问题是:在我当前的实现中,如果我在 expirationHandler 块中调用 endBackgroundTask: 并且我的调度队列的任务没有完成怎么办?我的应用将被终止或暂停?

谢谢

【问题讨论】:

如果您在队列的任何位置调用 endBackgroundTask:。它只是暂停了您的应用程序,使应用程序进入睡眠状态。 @chiragshah 所以这不能成为看门狗杀死我的应用程序的原因吗?只有当我错过endBackgroundTask 时,看门狗才会终止应用程序,对吗? 是的,我也认为在我的应用程序中它可以正常工作 【参考方案1】:

这里有一些场景,你在使用beginBackgroundTaskWithExpirationHandler 时需要处理,否则你的应用会terminate

场景 1:您的应用在 Foreground 中运行。你开始beginBackgroundTaskWithExpirationHandler 然后进入Background 模式。您的应用可以长期存活。

场景 2:您的应用在 Foreground 中运行。你开始beginBackgroundTaskWithExpirationHandler 然后进入Background 模式。然后回到Foreground模式并且你没有调用endBackgroundTask那么你的应用程序仍然execute background queue所以它将扩展下一个3 minute的进程(在ios 7引入之后。在IOS 7之前,进程执行时间是10分钟)。所以你必须取消后台队列,任务从后台队列出来,进入前台队列

下面是向您展示的代码。处理后台进程的最佳方法是什么。

第 1 步: 将 __block UIBackgroundTaskIdentifier bgTask 声明为全局变量。

第 2 步: 在 applicationDidEnterBackground 中添加以下代码。

- (void)applicationDidEnterBackground:(UIApplication *)application 

         bgTask = [application beginBackgroundTaskWithExpirationHandler:^
         bgTask = UIBackgroundTaskInvalid;
          ];


第 3 步:一旦应用进入前台模式,就停止后台任务处理程序。

- (void)applicationWillEnterForeground:(UIApplication *)application 
  // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.

  [[UIApplication sharedApplication] endBackgroundTask:bgTask];


【讨论】:

感谢您的回答。在场景2中,如果我在应用程序进入前台时不调用endBackgroundTask怎么办?我已经在过期块中调用 endBackgroundTask。 编译器将理解您的后台队列仍在执行,因此它的范围执行到该编译器终止您的应用程序后的下一个 3 分钟。这是发生在我身上的情景。【参考方案2】:

如果您不在过期处理程序中调用endBackgroundTask,您的应用将被终止。

一旦您在过期处理程序中调用 endBackgroundTask,您只是告诉操作系统“好的,我已完成保存关键任务。现在由您决定。”之后,您的应用可能会暂停一段时间并稍后终止,也可能会立即终止,具体取决于系统资源。

【讨论】:

【参考方案3】:

您的演示代码的问题是您在用户任务过程中丢失了 endBackgroundTask。

你应该使用如下:

__block UIBackgroundTaskIdentifier task = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
    [[UIApplication sharedApplication] endBackgroundTask:task];
    task = UIBackgroundTaskInvalid;
];
dispatch_queue_t queue = dispatch_queue_create("com.test.test1234", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^
    // My Task goes here

    // add new
    [UIApplication sharedApplication] endBackgroundTask:task];
    task = UIBackgroundTaskInvalid;
);

【讨论】:

【参考方案4】:

@Jatin Patel 答案很好。只需使用以下代码更改ApplicationWillEnterForeground 的实现,以便您的应用程序永远不会终止

DispatchQueue.global(qos: .background).async 
    // sends registration to background queue
    application.endBackgroundTask(self.bgTask)
    self.bgTask = UIBackgroundTaskIdentifier.invalid

【讨论】:

【参考方案5】:

我的应用在后台播放音乐,但出现此错误。

[BackgroundTask] 后台任务 1(“由 UIKitCore 调用,来自 __47-[UIApplication _applicationDidEnterBackground]block_invoke”)是在 30 多秒前创建的。在后台运行的应用程序中,这会产生终止风险。请记住及时为您的任务调用 UIApplication.endBackgroundTask(:) 以避免这种情况。

我使用以下代码修复了它:

- (void)applicationDidEnterBackground:(UIApplication *)application

    backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^
        [[UIApplication sharedApplication] endBackgroundTask:self->backgroundTask];
        self->backgroundTask = UIBackgroundTaskInvalid;
     ];

【讨论】:

以上是关于beginBackgroundTaskWithExpirationHandler 调用 endBackgroundTask 但未结束进程的主要内容,如果未能解决你的问题,请参考以下文章