With Swift 3.1 on XCode 8.3, running the following code with the Thread Sanitizer finds a data race (see the write and read comments in the code):
private func incrementAsync() {
let item = DispatchWorkItem { [weak self] in
guard let strongSelf = self else { return }
strongSelf.x += 1 // <--- the write
// Uncomment following line and there's no race, probably because print introduces a barrier
//print("> DispatchWorkItem done")
}
item.notify(queue: .main) { [weak self] in
guard let strongSelf = self else { return }
print("> \(strongSelf.x)") // <--- the read
}
DispatchQueue.global(qos: .background).async(execute: item)
}
This seems pretty strange to me as the documentation for the DispatchWorkItem
mentions that it allows:
getting notified about their completion
which implies that the notify
callback is called once the work item's execution is done.
So I would expect that there would be a happens-before
relationship between the DispatchWorkItem
's work closure and its notify closure. What would be the correct way, if any, to use a DispatchWorkItem
with a registered notify
callback like this that wouldn't trigger the Thread Sanitizer error?
I tried registering the notify
with item.notify(flags: .barrier, queue: .main) ...
but the race persisted (probably because the flag only applies to the same queue, documentation is sparse on what the .barrier
flag does). But even calling notify on the same (background) queue as the work item's execution, with the flags: .barrier
, results in a race.
If you wanna try this out, I published the complete XCode project on github here: https://github.com/mna/TestDispatchNotify
There's a TestDispatchNotify
scheme that builds the app without tsan, and TestDispatchNotify+Tsan
with the Thread Sanitizer activated.
Thanks, Martin