如何在Swift中创建自定义完成块

如何在Swift中创建自定义完成块,swift,multithreading,asynchronous,Swift,Multithreading,Asynchronous,我正在做一些后期处理 a。一旦我的一个异步函数完成 b。一旦我所有的异步函数都完成了 不幸的是,我在下面的代码中遇到了竞争条件 func foo(stuff: AnArrayOfObjects, completed: (NSError?)->()) { // STEP 1 OF CREATING AN OVERALL COMPLETION BLOCK: Create a dispatch group. let loadServiceGroup: dispatch_group

我正在做一些后期处理

a。一旦我的一个异步函数完成

b。一旦我所有的异步函数都完成了

不幸的是,我在下面的代码中遇到了竞争条件

func foo(stuff: AnArrayOfObjects, completed: (NSError?)->()) {
    // STEP 1 OF CREATING AN OVERALL COMPLETION BLOCK: Create a dispatch group.
    let loadServiceGroup: dispatch_group_t = dispatch_group_create()

    // Define errors to be processed when everything is complete.
    // One error per service; in this example we'll have two
    let configError: NSError? = nil
    let preferenceError: NSError? = nil

    // some more preprocessing / variable declarations here. E.g.:
    var counter = 0

    // STEP 2 OF CREATING AN OVERALL COMPLETION BLOCK: Adding tasks to a dispatch group
    dispatch_group_enter(loadServiceGroup)

    // here i may start MULTPILE functions that are asynchronous. For example:
    for i in stuff {
       estore.fetchRemindersMatchingPredicate(remindersPredicate) {
            // MARK: Begininning of thread

            // does something here. E.g., count the elements:
            counter += 1

            // update the UI                    
            dispatch_async(dispatch_get_main_queue()) {
               self.sendChangedNotification()        // reloads the tableview.
               // WARNING: I CAN'T JUST SHOW THE counter RESULTS HERE BECAUSE IT MIGHT NOT BE DONE YET. IT IS ASYNCHRONOUS, IT MIGHT STILL BE RUNNING.
            }
        }

        // STEP 3 OF CREATING AN OVERALL COMPLETION BLOCK: Leave dispatch group. This must be done at the end of the completion block.
        dispatch_group_leave(loadServiceGroup)

        // MARK: End of thread
    }

    // STEP 4 OF CREATING AN OVERALL COMPLETION BLOCK: Acting when the group is finished
    dispatch_group_notify(loadServiceGroup, dispatch_get_main_queue(), {
        // do something when asychrounous call block (above) has finished running. E.g.:
        print("number of elements: \(counter)")

        // Assess any errors
        var overallError: NSError? = nil;

        if configError != nil || preferenceError != nil {
            // Either make a new error or assign one of them to the overall error.
            overallError = configError ?? preferenceError
        }

        // Call the completed function passed to foo. This will contain additional stuff that I want executed in the end.
        completed(overallError)
    })   
}

老实说,我不完全明白你在做什么。但是,我总是使用一个计数器变量来确定所有异步函数是否完成

func foo(completionHandler: (success: Bool) -> Void) {
    var numberOfTasks = 0
    for data in datasource {
        // do something preparing
        numberOfTasks += 1
    }

    var numberOfDones = 0
    objc_sync_enter(numberOfDones)
    data.foo(completionHandler:(){
        // do something handling outcome
        numberOfDones += 1
        if numberOfDones == numberOfTasks {
            completionHandler(true)
        }
    })
    objc_sync_exit(numberOfDones)
}
其机制是我们知道要完成的任务总数,对于每个任务,我们可以捕获完成事件,因此我们添加了
numberOfDones
。因此,无论何时
numberOfDones==numberOfTasks
我们都会知道这是最后一个任务,而且已经完成了

根据你的代码,我试着在上面应用这个想法

func foo(stuff: AnArrayOfObjects, completed: ([NSError]?)->()) {
    // STEP 1 OF CREATING AN OVERALL COMPLETION BLOCK: Create a dispatch group.
    let loadServiceGroup: dispatch_group_t = dispatch_group_create()

    // Define errors to be processed when everything is complete.
    // One error per service; in this example we'll have two
    let configError: NSError? = nil
    let preferenceError: NSError? = nil

    // some more preprocessing / variable declarations here. E.g.:
    var counter = 0

    // STEP 2 OF CREATING AN OVERALL COMPLETION BLOCK: Adding tasks to a dispatch group
    dispatch_group_enter(loadServiceGroup)

    // here i may start MULTPILE functions that are asynchronous. For example:
    for i in stuff {
        estore.fetchRemindersMatchingPredicate(remindersPredicate) {
            // MARK: Begininning of thread

            // does something here. E.g., count the elements:
            counter += 1

            // update the UI
            dispatch_async(dispatch_get_main_queue()) {
                self.sendChangedNotification()        // reloads the tableview.
                // WARNING: I CAN'T JUST SHOW THE counter RESULTS HERE BECAUSE IT MIGHT NOT BE DONE YET. IT IS ASYNCHRONOUS, IT MIGHT STILL BE RUNNING.
            }
        }

        // STEP 3 OF CREATING AN OVERALL COMPLETION BLOCK: Leave dispatch group. This must be done at the end of the completion block.
        dispatch_group_leave(loadServiceGroup)

        // MARK: End of thread
    }

    var numberOfDones = 0
    var errors = [NSError]()

    // STEP 4 OF CREATING AN OVERALL COMPLETION BLOCK: Acting when the group is finished
    objc_sync_enter(numberOfDones)
    dispatch_group_notify(loadServiceGroup, dispatch_get_main_queue(), {
        // do something when asychrounous call block (above) has finished running. E.g.:
        print("number of elements: \(counter)")

        // Assess any errors
        var overallError: NSError? = nil;

        if configError != nil || preferenceError != nil {
            // Either make a new error or assign one of them to the overall error.
            errors.append(configError ?? preferenceError!)
        }

        numberOfDones += 1
        if numberOfDones == counter {
            // Call the completed function passed to foo. This will contain additional stuff that I want executed in the end.
            completed(errors)
        }
    })
    objc_sync_exit(numberOfDones)
}
编辑:

感谢@CouchDeveloper对我的答案发表了评论。我想出了另一个解决方案,通过同步计数器变量使线程安全。答案已更新,下面是解决方案的实验


您应该添加您期望的内容和您实际体验的内容。有一个潜在的数据竞争:IFF语句
counter+=1
将在不同的队列上执行(更准确地说,没有相同的父队列),然后可能在不同的线程上访问它,这在本例中是一个数据竞争。不幸的是,您的第一个代码段有一个潜在的数据竞争,这正是OP试图避免的。如果对变量
count
的访问是同步的,则可以使用变量来计算调用次数。例如,让所有访问在串行调度队列上执行。由于完成处理程序的“执行上下文”未知,因此第一个代码示例具有风险,应该避免。因此,您随后的解决方案也不正确。@CouchDeveloper感谢您的评论。我还是不明白为什么我的想法有风险。请看我的编辑,这是我理论的证明。由于我已经在我的几个项目中使用了这个想法,请纠正我,如果我错了,我们将不胜感激。到目前为止还并没有相关的bug报告,但线程安全可能是一个严重的问题。如果您能纠正我的错误,非常感谢。如果两个或多个线程访问一个共享变量而没有同步,并且至少有一个访问是写入的,则会发生数据争用。我在gist上放了一段代码片段,演示了数据竞争。@CouchDeveloper非常感谢您的示例。现在我对“数据竞赛”有了更好的理解。根据它的定义,我通过同步计数器变量得出了一个解决方案。你认为这个解决方案真的完美地解决了这个问题吗?这是我的朋友,
objc\u sync\u enter
基本上是一个互斥对象,可以用来同步对共享变量的访问,在这种情况下,您不应该以这种方式进行同步。如果您看一下我的示例,只需使用另一个队列,它是一个串行队列(而不是并发队列),并且被注释掉。