2016-03-29 67 views
0

我正在嘗試做一些後期處理如何在Swift中創建自定義完成塊

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) 
    }) 
} 
+0

您應該添加你是什麼_expecting_,以及你正在遭受的實驗。有一種潛在的數據競爭:IFF語句「counter + = 1」將在不同的隊列上執行(更準確地說,沒有相同的父隊列),那麼它可能會在不同的線程上訪問,這種情況下是數據競爭。 – CouchDeveloper

回答

0

說實話,我並不完全明白你在做什麼。不過,我總是使用一個計數器變量來確定是否所有的異步函數都完成了。

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評論我的答案。我制定了另一個通過同步計數器變量使線程安全的解決方案。答案是更新,下面是解決

https://gist.github.com/qiuyujx/7173ea663308cc03f07e8a5c09cf4cba

+0

不幸的是,你的第一個代碼片段有潛在的數據競爭,這正是OP試圖避免的。如果對變量count的訪問是同步的,您可以使用一個變量來計算調用次數。例如,讓所有訪問都在串行調度隊列上執行。由於完成處理程序的「執行上下文」未知,因此此第一個代碼示例存在風險,應予以避免。因此,您的後續解決方案也不正確。 – CouchDeveloper

+0

@CouchDeveloper感謝您的評論。我仍然不明白爲什麼我的想法有風險。請看我的編輯,這是我的理論的證明。由於我將這個想法用於我的幾個項目,請糾正我,如果我錯了,我將非常感激。到目前爲止沒有相關的漏洞報告,但是線程安全可能是嚴重的問題。如果你能糾正我的話,非常感謝。 – Christopher

+0

如果兩個或多個線程在沒有同步的情況下訪問共享變量,並且至少有一個訪問是寫入,則會發生數據競爭。我在gist https://gist.github.com/couchdeveloper/ca040feec33ab5026ff2上放置了一段代碼片段,它演示了數據競賽。 – CouchDeveloper

相關問題