iosswiftnsoperationnsblockoperation

Block Operation - Completion Block returning random results


My block operation completion handler is displaying random results. Not sure why. I've read this and all lessons say it is similar to Dispatch Groups in GCD

Please find my code below

import Foundation

let sentence = "I love my car"
let wordOperation = BlockOperation()
var wordArray = [String]()

for word in sentence.split(separator: " ") {
  wordOperation.addExecutionBlock {
  print(word)
wordArray.append(String(word))
 }
}

wordOperation.completionBlock = {
    print(wordArray)
    print("Completion Block")
}

wordOperation.start()

I was expecting my output to be ["I", "love", "my", "car"] (it should display all these words - either in sequence or in random order)

But when I run my output is either ["my"] or ["love"] or ["I", "car"] - it prints randomly without all expected values

Not sure why this is happening. Please advice

enter image description here

enter image description here


Solution

  • The problem is that those separate execution blocks may run concurrently with respect to each other, on separate threads. This is true if you start the operation like you have, or even if you added this operation to an operation queue with maxConcurrentOperationCount of 1. As the documentation says, when dealing with addExecutionBlock:

    The specified block should not make any assumptions about its execution environment.

    On top of this, Swift arrays are not a thread-safe. So in the absence of synchronization, concurrent interaction with a non-thread-safe object may result in unexpected behavior, such as what you’ve shared with us.

    If you turn on TSAN, the thread sanitizer, (found in “Product” » “Scheme” » “Edit Scheme...”, or press +<, and then choose “Run” » “Diagnostics” » “Thread Sanitizer”) it will warn you about the data race.


    So, bottom line, the problem isn’t addExecutionBlock, per se, but rather the attempt to mutate the array from multiple threads at the same time. If you used concurrent queue in conjunction with dispatch group, you can experience similar problems (though, like many race conditions, sometimes it is hard to manifest).

    Theoretically, one could add synchronization code to your code snippet and that would fix the problem. But then again, it would be silly to try to initiate a bunch of concurrent updates, only to then employ synchronization within that to prevent concurrent updates. It would work, but would be inefficient. You only employ that pattern when the work on the background threads is substantial in comparison to the amount of time spent synchronizing updates to some shared resource. But that’s not the case here.