在继续之前等待完成处理程序完成

时间:2017-07-14 04:37:20

标签: ios multithreading grand-central-dispatch semaphore

我有一组9张图片,我想将它们全部保存到用户的相机胶卷中。您可以使用UIImageWriteToSavedPhotosAlbum执行此操作。我写了一个循环来保存每个图像。这个问题是由于某种原因,它会only save the first five。现在,顺序很重要,所以如果图像无法保存,我想重试并等到它成功,而不是有一些不可预测的种族。

所以,我实现了一个完成处理程序,并认为我可以像这样使用信号量:

func save(){
    for i in (0...(self.imagesArray.count-1)).reversed(){
        print("saving image at index ", i)
        semaphore.wait()

        let image = imagesArray[i]
        self.saveImage(image)

    }
}

func saveImage(_ image: UIImage){
    UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
}

func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
    //due to some write limit, only 5 images get written at once.
    if let error = error {
        print("trying again")
        self.saveImage(image)
    } else {
        print("successfully saved")
        semaphore.signal()
    }
}

我的代码的问题是它在第一次保存后被阻止,semaphore.signal永远不会被调用。我认为我的完成处理程序应该在主线程上调用,但是已经被semaphore.wait()阻止了。任何帮助赞赏。感谢

3 个答案:

答案 0 :(得分:1)

正如其他人所指出的,你想避免等待主线程,冒着死锁的风险。因此,虽然您可以将其推送到全局队列,但另一种方法是使用众多机制之一来执行一系列异步任务。选项包括异步Operation子类或承诺(例如PromiseKit)。

例如,要将图像保存任务包装在异步Operation中并将其添加到OperationQueue,您可以像这样定义图像保存操作:

class ImageSaveOperation: AsynchronousOperation {

    let image: UIImage
    let imageCompletionBlock: ((NSError?) -> Void)?

    init(image: UIImage, imageCompletionBlock: ((NSError?) -> Void)? = nil) {
        self.image = image
        self.imageCompletionBlock = imageCompletionBlock

        super.init()
    }

    override func main() {
        UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
    }

    func image(_ image: UIImage, didFinishSavingWithError error: NSError?, contextInfo: UnsafeRawPointer) {
        imageCompletionBlock?(error)
        complete()
    }

}

然后,假设您有一个数组images,即[UIImage],那么您可以这样做:

let queue = OperationQueue()
queue.name = Bundle.main.bundleIdentifier! + ".imagesave"
queue.maxConcurrentOperationCount = 1

let operations = images.map {
    return ImageSaveOperation(image: $0) { error in
        if let error = error {
            print(error.localizedDescription)
            queue.cancelAllOperations()
        }
    }
}

let completion = BlockOperation {
    print("all done")
}
operations.forEach { completion.addDependency($0) }

queue.addOperations(operations, waitUntilFinished: false)
OperationQueue.main.addOperation(completion)

显然,您可以自定义此选项以在出错时添加重试逻辑,但现在可能不需要这样做,因为“太忙”问题的根源是太多并发保存请求的结果,我们已将其消除。这只会留下不太可能通过重试解决的错误,所以我可能不会添加重试逻辑。 (错误更可能是权限失败,空间不足等。)但是,如果您真的想要,可以添加重试逻辑。更有可能的是,如果您有错误,您可能只想取消队列中的所有剩余操作,就像我上面一样。

注意,上面的子类AsynchronousOperation,它只是Operation子类,isAsynchronous返回true。例如:

/// Asynchronous Operation base class
///
/// This class performs all of the necessary KVN of `isFinished` and
/// `isExecuting` for a concurrent `NSOperation` subclass. So, to developer
/// a concurrent NSOperation subclass, you instead subclass this class which:
///
/// - must override `main()` with the tasks that initiate the asynchronous task;
///
/// - must call `completeOperation()` function when the asynchronous task is done;
///
/// - optionally, periodically check `self.cancelled` status, performing any clean-up
///   necessary and then ensuring that `completeOperation()` is called; or
///   override `cancel` method, calling `super.cancel()` and then cleaning-up
///   and ensuring `completeOperation()` is called.

public class AsynchronousOperation : Operation {

    private let syncQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".opsync")

    override public var isAsynchronous: Bool { return true }

    private var _executing: Bool = false
    override private(set) public var isExecuting: Bool {
        get {
            return syncQueue.sync { _executing }
        }
        set {
            willChangeValue(forKey: "isExecuting")
            syncQueue.sync { _executing = newValue }
            didChangeValue(forKey: "isExecuting")
        }
    }

    private var _finished: Bool = false
    override private(set) public var isFinished: Bool {
        get {
            return syncQueue.sync { _finished }
        }
        set {
            willChangeValue(forKey: "isFinished")
            syncQueue.sync { _finished = newValue }
            didChangeValue(forKey: "isFinished")
        }
    }

    /// Complete the operation
    ///
    /// This will result in the appropriate KVN of isFinished and isExecuting

    public func complete() {
        if isExecuting { isExecuting = false }

        if !isFinished { isFinished = true }
    }

    override public func start() {
        if isCancelled {
            isFinished = true
            return
        }

        isExecuting = true

        main()
    }
}

现在,我很欣赏操作队列(或承诺)对你的情况来说似乎有些过分,但它是一个有用的模式,你可以在任何有一系列异步任务的地方使用它。有关操作队列的更多信息,请随时参考Concurrency Programming Guide: Operation Queues

答案 1 :(得分:0)

一年前我面临同样的问题

您应该尝试将代码放在Dispatach.global队列中,这肯定会有帮助

原因:我真的不知道原因,我认为它是什么信号量,可能需要在后台线程中执行以同步等待和信号

答案 2 :(得分:0)

正如迈克改变提到的那样,使用Dispatch.global().async帮助解决了这个问题。代码现在看起来像这样:

func save(){
    for i in (0...(self.imagesArray.count-1)).reversed(){
        DispatchQueue.global().async { [unowned self] in
            self.semaphore.wait()

            let image = self.imagesArray[i]
            self.saveImage(image)
        }
    }
}

我怀疑问题是完成处理程序在主线程中执行,主线程已被最初调用的semaphore.wait()锁定。因此,当完成时,semaphore.signal()永远不会被调用。

这可以通过将任务运行为异步队列来解决。