后台任务到期处理程序和NSOperationQueue(我们需要等待操作完成吗?)

时间:2015-10-12 14:31:52

标签: ios nsoperation nsoperationqueue background-task

当调用过期处理程序(在主线程上同步)时,我们必须快速完成/取消我们的任务以防止应用程序被终止。

我读到某处(但找不到引用)所有处理必须在到期块返回时完全完成。

这是否意味着,如果我使用NSOperationQueue,我必须取消,然后在返回之前等待操作完成?像这样:

backgroundTaskIdentifier = UIApplication.sharedApplication().beginBackgroundTaskWithName("MyBackgroundTask") {
    operationQueue.cancelAllOperations()
    operationQueue.waitUntilAllOperationsAreFinished() // Wait, so everything finishes before returning from this func (but might deadlock!)
    UIApplication.sharedApplication().endBackgroundTask(backgroundTaskIdentifier)
}

这意味着锁定主线程,这可能会导致某些需要后台线程死锁的任务。

被取消的NSOperation可能需要少量时间(甚至可能是一两秒钟)来正确取消,所以我们怎样才能确保安全地确保它在到期时真正完成处理程序被调用,没有意外死锁?

1 个答案:

答案 0 :(得分:0)

当您在主线程上调用operationQueue.waitUntilAllOperationsAreFinished()时,它将被阻止,并且从其他线程调度到主线程的委托或其他代码将导致死锁。

这是少数情况下你必须“忙等待”并且轮询(线程安全!)某种标志,表明任务已经完成。幸运的是,您可以利用RunLoop而不会过多地压力CPU。使用运行循环在主线程上执行“等待”,您还可以实际执行在主线程上调度的委托或继续,而不会遇到死锁。

以下代码(使用Swift)显示了如何使用运行循环来完成主线程上的“忙等待”以及在主线程上执行的任务:< / p>

假设您有一些“future”代表异步任务的最终结果:

public protocol Future : class {        
    var isCompleted: Bool { get }
    func onComplete(f: ()->())        
}

理想情况下,您可以“注册”(一个或多个)在将来完成时调用的完成处理程序。

现在我们可以为将来定义一个方法runloopWait,它在一个线程(必须有一个RunLoop)上“等待”直到将来完成:

extension Future {

    public func runLoopWait() {
        // The current thread MUST have a run loop and at least one event source!
        // This is difficult to verfy in this method - thus this is simply
        // a prerequisite which must be ensured by the client. If there is no
        // event source, the run lopp may quickly return with the effect that the
        // while loop will "busy wait".

        var context = CFRunLoopSourceContext()
        let runLoop = CFRunLoopGetCurrent()
        let runLoopSource = CFRunLoopSourceCreate(nil, 0, &context)

        CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode)
        self.onComplete() {
            CFRunLoopStop(runLoop);
        }

        while !self.isCompleted {
            CFRunLoopRun()
        }

        CFRunLoopRemoveSource(runLoop, runLoopSource, kCFRunLoopDefaultMode)
    }

}

使用此代码,只有在调用完成处理程序时才会返回运行循环。也就是说,你实际上没有等待,而是某种 async 等待。

下面的代码完成了模拟未来的示例,您可以运行该示例来检查上述解决方案。请注意,未来的实现不是线程安全的,但应该在此示例中起作用。

public class Task : Future {

    var _completed = false

    let _task: () -> ()
    var _continuation: ()->()

    public init(task: () -> ()) {
        _task = task
        _continuation = {}
    }

    public var isCompleted: Bool {
        return _completed
    }

    public static func start(t: Task, queue: dispatch_queue_t ) {
        dispatch_async(queue) {
            t._task()
            t._completed = true
            t._continuation()
        }
    }

    public func onComplete(f:()->()) {
        _continuation = f
    }

}



let t = Task() {

    for _ in 0...5 {
        sleep(1)
        print(".", terminator: "")
    }
    print("")
}

// run the task on the main queue:
Task.start(t, queue: dispatch_get_main_queue())

t.runLoopWait() // "wait" on the main queue
print("main thread finished")