2016-03-29 5 views
0

私は後処理をやろうとしていますSwiftでカスタム補完ブロックを作成する方法

a。私の非同期機能の1つが終了したら

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

EDIT:@CouchDeveloperに

おかげで私の回答にコメントしました。私はカウンタ変数を同期させることによってスレッドを安全にする別の解決法を考え出します。答えは更新され、以下のソリューション

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

+0

残念ながら、最初のコードスニペットには潜在的なデータ競合があります。これはOPが避けようとしたものです。変数 'count'へのアクセスが同期されている場合は、変数を使用して呼び出しの数を数えます。たとえば、すべてのアクセスがシリアルディスパッチキュー上で実行されるようにします。補完ハンドラの「実行コンテキスト」は分かっていないため、この最初のコードサンプルにはリスクがあり、避けるべきです。結果として、その後の解決策も正しくありません。 – CouchDeveloper

+0

@CouchDeveloperコメントをいただきありがとうございます。なぜ私のアイデアが危険なのかまだ分かりません。私の理論の証明である私の編集を見てください。このアイデアをいくつかのプロジェクトに使用しているので、私を修正してください。私が間違っていれば、非常に高く評価されます。これまでに報告されたバグはありませんが、スレッドの安全性は深刻な問題になる可能性があります。あなたが私を正すことができれば、ありがとう。 – Christopher

+0

少なくとも1つのアクセスが書き込みである場合、2つ以上のスレッドが同期なしに共有変数にアクセスすると、データ競合が発生します。データの競合を示す要点https://gist.github.com/couchdeveloper/ca040feec33ab5026ff2にコードスニペットを記入します。 – CouchDeveloper

関連する問題