微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在单个全局线程上执行长时间运行的后台任务

如何解决在单个全局线程上执行长时间运行的后台任务

我正在开发一个允许用户对coredata进行批量更改的应用程序,并且我正在尝试确定解决此问题的最佳方法

设置

我有两个实体,一个是(Main)记录,另一个是(secondary)记录,它们与(Main)记录有一对多的关系。 这由主/详细样式设置中的2个表视图表示

例如如果用户点击(View控制器A)上的(Main)表行,则会通过一对多关系显示(View控制器B)上表中的(Secondary)数据。 (最多可以有数千条记录)

在(视图控制器B)中,用户可以选择不删除,不删除部分或全部(辅助)记录

您可以想象,如果有几千条记录,则可能需要一段时间。

我知道可以使用“批量删除”选项,但是在这种情况下,我想使用标准的context.delete(record)方法

我想出的解决方案是有一个单例,它有2个主要dispatchQueue,1个是顺序的,另一个是并发的,因为一些大容量更改需要按它们初始化的顺序完成,而有些则不需要t

我还想将这些队列注册为长时间运行的后台任务,以便在将应用程序发送到后台时使它们有更多的时间运行

我要达到的目的是,如果用户删除几批记录,它将删除请求发送到单例,然后将其添加到队列中,一旦队列中的所有项目都完成,它将通知后台任务已完成的系统,因此现在可以安全地挂起该应用了。

这是我用于单例的代码

class GlobalChanges {
    
    static let shared = GlobalChanges()
    
    // MARK: - Initialization
    private init() {}
    
    
    
    //MARK: - the global background queues that orgainise the background threads
    
    //Queue that performs tasks one after the other
    let serialQueue = dispatchQueue(label: "backgroundSerialQueue")
    let serialGroup = dispatchGroup()
    
    //the background task id of the serial queue
    //used to register it as a long running background task
    var serialBackgroundTaskId: uibackgroundtaskIdentifier? = .invalid
   
    //Queue that performs tasks concurrently
    let concurrentQueue = dispatchQueue(label: "backgroundConcurrentQueue",attributes: .concurrent)
    let concurrentGroup = dispatchGroup()
    
    //the background task id of the concurrent queue
    //used to register it as a long running background task
    var concurrentBackgroundTaskId: uibackgroundtaskIdentifier? = .invalid
    
    
    
    
    
    //MARK: - Delete Records
    func deleteRecords(records: [NSManagedobject],managedContext: NSManagedobjectContext,finished: @escaping () -> Void){
        
        //create a new background MOC
        let coreDataManager = CoreDataStack.shared
        let backgroundContext = coreDataManager.persistentContainer.newBackgroundContext()
        backgroundContext.automaticallyMergesChangesFromParent = true
        
        //register the queue as a long running background task,if it's not already registered
        if(self.concurrentBackgroundTaskId == .invalid){
            self.concurrentBackgroundTaskId = UIApplication.shared.beginBackgroundTask(
                withName: "concurrent background task",expirationHandler: {
                    
                    //add the context save to the background save queue
                    coreDataManager.SaveBackgroundContext(backgroundContext: backgroundContext,mainContext: managedContext)
                    
                    //end the background task
                    UIApplication.shared.endBackgroundTask(self.concurrentBackgroundTaskId!)
                    self.concurrentBackgroundTaskId = uibackgroundtaskIdentifier.invalid
            })
        }
        
        
        //enter the group
        concurrentGroup.enter()
        
        //perform task asynchronously
        concurrentQueue.async{
            
            sleep(10)//<-- here just to help debug
              
            backgroundContext.performAndWait {
                
                
                //remove the records from the managed context
                for record in records{
                    
                    //Get the record by it's ID,so it can be manipulated safely
                    let backgroundContextRecord = backgroundContext.object(with: record.objectID) as NSManagedobject
                    
                  
                    //delete the record
                    backgroundContext.delete(backgroundContextRecord)
                    
                }
                
                //add the context save to the background save queue
                coreDataManager.SaveBackgroundContext(backgroundContext: backgroundContext,mainContext: managedContext)
                
                //set operation has finished
                finished()
                
                print("Finished delete")
                
                //leave te group
                self.concurrentGroup.leave()
                
                //If all items in the queue have finished
                self.concurrentGroup.notify(queue: self.concurrentQueue) {
                    
                    print("Task ended")
                    
                    //end long running task
                    UIApplication.shared.endBackgroundTask(self.concurrentBackgroundTaskId!)
                    self.concurrentBackgroundTaskId = uibackgroundtaskIdentifier.invalid
                    
                }
            }
        }
    }
    

    
    
    
}

当我使用两个删除请求运行此代码时,得到以下输出

Finished delete
Finished delete
Task ended
Task ended
2020-08-27 08:11:53.343126+0100 Burn[9643:3585892] Can't end BackgroundTask: no background task exists with identifier 3 (0x3),or it may have already been ended. Break in UIApplicationEndBackgroundTaskError() to debug.

它按预期运行,并在队列中的所有项目完成后调用“任务已结束”,但似乎被调用了两次,这会导致尝试结束不存在的后台任务时出错

我将如何使其仅被调用一次?

我知道我可以添加一个检查以查看任务是否已经结束,如果这样做,不要尝试再次结束它,但是我希望仅在所有任务完成后才调用它。

此外,我还是Swift的新手,所以这个解决方案是个好主意,还是潜在的灾难等待发生?

任何帮助将不胜感激

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。