2012-11-08 45 views
4

我在阐述支持NSOperationQueue类的背景的有用策略时遇到了一些问题。特别是,我有一堆NSOperation s表示执行以下操作:NSOperationQueue和背景支持

  • 从网络
  • 解析文件
  • 导入数据文件中的核心数据

的下载文件操作被插入到串行队列中。一旦操作完成,下一个可以开始。

我需要在应用程序进入后台时停止(或继续)操作。从这些讨论(Does AFNetworking have backgrounding support?Queue of NSOperations and handling application exit)我看到最好的办法是取消操作并在每个操作中使用isCancelled属性。然后,根据该属性检查操作的关键点,它允许在应用程序进入后台时回滚执行状态(正在运行的操作)。

基于强调背景支持的Apple模板,我该如何管理类似的情况?我可以简单地取消操作还是等待当前操作完成?详情请参阅评论。

- (void)applicationDidEnterBackground:(UIApplication *)application 
{ 
    bgTask = [application beginBackgroundTaskWithExpirationHandler:^{ 

     // Do I have to call -cancelAllOperations or 
     // -waitUntilAllOperationsAreFinished or both? 

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

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

     // What about here? 

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

在此先感谢您。

编辑

如果NSOperationmain方法执行下面的代码,怎么可能跟随响应取消事件模式?

- (void)main 
{ 
    // 1- download 

    // 2- parse 
    // 2.1 read file location 
    // 2.2 load into memory 

    // 3- import 
    // 3.1 fetch core data request 
    // 3.2 if data is not present, insert it (or update) 
    // 3.3 save data into persistent store coordinator 
} 

我描述的每种方法都包含各种步骤(非原子操作,除下载之外)。因此,在每个步骤中都可能会发生取消(以未预定义的方式)。我可以在每一步之前检查isCancelled属性吗?这是否工作?基于Tammo福瑞斯”编辑

我明白你的意思是你编辑代码

编辑2。但我担心的是以下内容。取消请求(用户可以按主页按钮)可能会在main执行过程中的任何位置发生,所以,如果我简单地返回,操作状态将被破坏。我需要在返回之前清理它的状态吗?你怎么看?

当我使用同步操作(在同一个线程内以同步方式执行的操作)时,我描述的问题可能会发生。例如,如果main正在下载文件(通过+sendSynchronousRequest:returningResponse:error执行下载),并且该应用程序置于后台,则会发生什么情况?如何管理这种情况?

// download 
if ([self isCancelled]) 
    return; 

// downloading here <-- here the app is put in background 

很明显,我认为当应用程序被放在前台时,操作会再次运行,因为它已被取消。换句话说,它被迫不保持其状态。我错了吗?

回答

15

如果我理解正确的话,你有一个NSOperationQueue,如果你的应用程序进入后台,你想

  1. 取消所有的操作和
  2. 等待,直到取消处理。

通常这不应该花太多时间,所以应该足以做到这一点:

- (void)applicationDidEnterBackground:(UIApplication *)application 
{ 
    [_queue cancelAllOperations]; 
    [_queue waitUntilAllOperationsAreFinished]; 
} 

的“太多时间”这里的定义是大约五秒钟:如果您阻止-applicationDidEnterBackground:长除此之外,你的应用程序将被终止并从内存中清除。

假设完成取消的操作需要5秒以上的时间。然后你要做的等待在后台(见说明中的注释):

- (void)applicationDidEnterBackground:(UIApplication *)application 
{ 
    bgTask = [application beginBackgroundTaskWithExpirationHandler:^{ 
     // If this block is called, our background time of normally 10 minutes 
     // is almost exceeded. That would mean one of the cancelled operations 
     // is not finished even 10 minutes after cancellation (!). 
     // This should not happen. 
     // What we do anyway is tell iOS that our background task has ended, 
     // as otherwise our app will be killed instead of suspended. 
     [application endBackgroundTask:bgTask]; 
     bgTask = UIBackgroundTaskInvalid; 
    }]; 

    // Normally this one is fast, so we do it outside the asynchronous block. 
    [_queue cancelAllOperations]; 

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
     // Wait until all the cancelled operations are finished. 
     [_queue waitUntilAllOperationsAreFinished]; 

     // Dispatch to the main queue if bgTask is not atomic 
     dispatch_async(dispatch_get_main_queue(), ^{ 
      [application endBackgroundTask:bgTask]; 
      bgTask = UIBackgroundTaskInvalid; 
     }); 
    }); 
} 

所以基本上我们告诉我们需要一些时间来执行任务的iOS,当任务完成时运行的我们告诉iOS我们的任务已经结束。

编辑

要回答你编辑的问题:为了应对取消,只是检查取消时,您可以和来自-main方法返回。取消的操作不会立即完成,但在-main返回时完成。

- (void)main 
{ 
    // 1- download 
    if ([self isCancelled]) return; 

    // 2- parse 
    // 2.1 read file location 

    // 2.2 load into memory 
    while (![self isCancelled] && [self hasNextLineToParse]) { 
     // ... 
    } 

    // 3- import 

    // 3.1 fetch core data request 
    if ([self isCancelled]) return; 


    // 3.2 if data is not present, insert it (or update) 
    // 3.3 save data into persistent store coordinator 
} 

如果你没有为取消标志检查-main可言,操作不会发生反应,消除,但运行,直到它完成。

编辑2

如果操作被取消,没有任何反应它除了isCancelled标志设置为true。上述原始答案中的代码将在后台等待,直到操作完成(对取消作出反应或完成,假设不需要10分钟即可取消)。

当然,在我们的操作中,当对isCancelled作出反应时,您必须确保将操作保持在未损坏状态,例如,直接在下载后(仅忽略数据)或写完所有数据后。

如果一个操作被取消但仍然在运行,当你切换回前台时,操作将完成下载,然后(如果你这样编程)反应取消并基本丢弃下载数据。

你可以做的是不取消操作,但等待它们完成(假设它们不到10分钟)。要做到这一点,只需删除行[_queue cancelAllOperations];

+0

+1您的回复。关于你的理解,是的,这是正确的。但是,你认为还有其他解决方案可以实现吗?在一个操作的* main *中,我执行三个不同的任务。那么,怎样才能以正确的方式取消手术呢?关于你的回复的第二部分,我无法理解*如果这个块被调用,我们的背景时间通常是10分钟 几乎超过*评论。你能说一下吗?最后,第二部分(* dispatch_async *)执行什么?你为什么要检索主线程?感谢您的支持。 –

+0

Hi @flexaddicted,要以正确的方式取消操作,您应该对文档中描述的取消事件做出反应:http://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationObjects/ OperationObjects.html#// apple_ref/doc/uid/TP40008091-CH101-SW24。 –

+1

关于第二部分:如果您使用iOS注册后台任务,通常会让您花10分钟完成它。当你的任务完成时,通过调用'-endBackgroundTask:'让iOS知道。假设您的操作在10分钟后仍未完成。只有在这种情况下,iOS才会调用过期处理程序块给你一个清理机会。如果你不在该块中调用'-endBackgroundTask:',你的应用将会被杀死,因为它行为不当。你必须平衡对'-beginBackgroundTaskWithExpirationHandler:'和'-endBackgroundTask:'的调用。 –