了解后台任务执行语法和GCD

时间:2013-11-23 09:57:11

标签: ios objective-c uikit grand-central-dispatch background-process

我正在尝试完全理解我在iOS中研究后台任务后我放在一起的下面代码,并希望得到一些帮助,

我理解基本概念,

首先我们得到app singleton,然后我们创建一个块并向系统注册后台任务,最后我们异步调度任务来运行。

以下是我正在寻求帮助的部分:

  1. 当background_task分配了块时,实际的块没有我们想在其中运行的代码,只有它的完成处理程序中的清理代码,为什么会这样?

  2. 我理解dispatch_async基本上是启动一个新线程并开始处理块中的代码,但是这个dispatch_async请求中的哪个是引用的background_task?我没有看到系统如何理解我们想要在dispatch_async请求中执行的代码与我们之前注册的background_task有关。

  3. 为什么我们在background_task的完成处理程序中的dispatch_async块的末尾都需要清理代码?

  4. 很抱歉,如果这些是愚蠢的问题,但我只是没有得到语法,

    这是我拼凑的代码:

    UIApplication *application = [UIApplication sharedApplication]; //Get the shared application instance
    
    __block UIBackgroundTaskIdentifier background_task; //Create a task object
    
    background_task = [application beginBackgroundTaskWithExpirationHandler: ^ { //Register background_task    
                    [application endBackgroundTask: background_task]; //Tell the system that we are done with the tasks
                    background_task = UIBackgroundTaskInvalid; //Set the task to be invalid
                    //Above code called when endBackgroundTask is called
                }];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
                    //Perform your tasks that your application requires
    
                    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(updateText) userInfo:nil repeats:YES];
                    NSLog(@"\n\nRunning in the background!\n\n");
    
                    [application endBackgroundTask: background_task]; //End the task so the system knows that you are done with what you need to perform
                    background_task = UIBackgroundTaskInvalid; //Invalidate the background_task
                });
    

5 个答案:

答案 0 :(得分:4)

后台任务标识符与您在辅助线程上执行的工作之间没有任何关系。后台任务表示请求一些额外的运行时间。就这样。您需要结束它以告诉操作系统您已完成了您想要完成的工作。如果您未能在可用时间内执行此操作,您的应用将被终止。任务ID只是一个表示操作系统允许工作一段时间的权限的标记。

您必须在两个位置清理,到期处理程序和异步调度块的结尾,因为它们代表两种不同的事件。在您调度到并发队列的块中,您结束任务是因为您已经及时完成了工作,并且您希望让操作系统知道,以便它可以暂停您的应用程序;它不需要终止它。在到期处理程序中,这是您最后一次结束任务以防止您的应用程序被终止的机会。你还没有完成你的工作,但是你已经没时间了。如果你没有在那时结束后台任务,操作系统会终止你的应用程序。

顺便说一下,在调度队列上运行的任务中调度计时器将不起作用。计时器安排在线程的运行循环中。服务调度队列的工作线程可以随时终止,并且在任何情况下都不会运行它们的运行循环。

答案 1 :(得分:2)

这个问题被提出并回答似乎很长时间了。 我只想在这里做一个说明,因为我感觉用户伍德斯托克(可能是我们很多人)对“后台线程”和“后台任务”有点困惑。

  • “后台线程”正在考虑UI线程和后台线程的上下文。

  • “后台任务”正在考虑允许任务持续时间超过正常持续时间(iOS中为10秒)的上下文。 方法beginBackgroundTaskWithExpirationHandler:告诉iOS您需要更多时间来完成您正在做的任何事情,以防应用程序背景化(请参阅应用程序的生命周期概念)。在此次通话之后,如果您的应用程序处于后台运行,则在您致电endBackgroundTask之前仍会获得CPU时间:或者系统发出信号表示它将过期。

答案 2 :(得分:1)

为了清楚起见,我正在设置后台任务如下:

@interface myClass ()
    @property (nonatomic, assign) UIBackgroundTaskIdentifier backgroundUpdateTask;
@end

- (void)importantStuffToCompleteInBackground
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0),
    ^{
        [self beginBackgroundUpdateTask];

        // do important background stuff

        [self endBackgroundUpdateTask];
    });
}

- (void)beginBackgroundUpdateTask
{
    self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
        [self endBackgroundUpdateTask];
    }];
}

- (void)endBackgroundUpdateTask
{
    [[UIApplication sharedApplication] endBackgroundTask:self.backgroundUpdateTask];
    self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}

根据beginBackgroundTaskWithExpirationHandler: docs,只有在您没有及时通过调用重要背景填充代码块的情况下,在剩余背景时间达到0之前不久将调用该块。 dispatch_async中的endBackgroundTask

答案 3 :(得分:1)

您可能在任何时候都在应用中运行各种长时间运行的后台任务。有几种方法可以运行后台任务。

You could spawn a thread或者您可以使用Grand Central Dispatch to push a block onto a worker queue,这可能看起来像..

一样简单
dispatch_async( queue, ^{
 [self saveAllUserDataToServer];
} );

但不要被愚弄,涉及多线程的任何事情都是困难和危险的。这是一个很大的主题,如果您有特定的任务需要帮助,那么您的问题就不清楚了。

一个这样的危险是,您的应用程序可以由用户或系统随时停止。您已经开始的后台任务可能已完成一半,然后停止。这可能是灾难或只是不方便。

iOS提供了一种不会突然停止这些任务的方法。无论何时开始执行此类任务,您都必须让系统知道,然后在任务完成时告诉它,就像这样......

- (void)saveAllUserDataToServer {
  UIBackgroundTaskIdentifier bt = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^ {}];
  ... do the work ...
  [application endBackgroundTask: background_task];
}

..现在您知道在后台线程上调用-saveAllUserDataToServer是安全的,即使应用程序在中途关闭,它也会运行完毕。

一个警告是你只有一定的时间限制来完成任务..如果你花了太长时间调用ExpirationHandler块,你必须在这里正确清理。这就是设置代码看起来更像......

__block UIBackgroundTaskIdentifier bt = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler: ^ {
    .. task didn't complete within time limit.. do additional cleanup
    [[UIApplication sharedApplication] endBackgroundTask: background_task];
}];

答案 4 :(得分:0)

当我的应用程序因为后台不断运行的进程而进入后台时出现以下错误:

  

不能使用endBackgroundTask:标识符9不存在任何后台任务,或者它可能已经结束。中断UIApplicationEndBackgroundTaskError()以进行调试。

谢天谢地,我遇到了hooleyhoop的答案,我能够用这个代码示例来解决这个问题:

func run() {
  DispatchQueue(label: "ping").async {
    let tid = UIApplication.shared.beginBackgroundTask(withName: "ping", expirationHandler: {})
    //network code
    //save data to database code
    UIApplication.shared.endBackgroundTask(tid)      
    run() //repeat process
  }
}

您也可以使用:

UIApplication.shared.beginBackgroundTask(expirationHandler:{}) 

如果你在调试时不需要一个好名字