I have 3 functions like this:
func getMyFirstItem(complete: @escaping (Int) -> Void) {
DispatchQueue.main.async {
complete(10)
}
}
func getMySecondtItem(complete: @escaping (Int) -> Void) {
DispatchQueue.global(qos:.background).async {
complete(10)
}
}
func getMyThirdItem(complete: @escaping (Int) -> Void) {
DispatchQueue.main.async {
complete(10)
}
}
And I have a variable:
var myItemsTotal: Int = 0
I would like to know how to sum the items, in this case 10 10 10 to get 30. But what is the best way, since is background and main.
2
Answers
I could be wrong, but I don’t think it makes much difference about the different queues,
you still have to "wait" until
the completions are done, for example:
The key issue is to ensure thread-safety. For example, the following is not thread-safe:
One could solve this problem by not allowing these tasks run in parallel, but you lose all the benefits of asynchronous processes and the concurrency they offer.
Needless to say, when you do allow them to run in parallel, you would likely add some mechanism (such as dispatch groups) to know when all of these asynchronous tasks are done. But I did not want to complicate this example, but rather keep our focus on the thread-safety issue. (I show how to use dispatch groups later in this answer.)
Anyway, if you have closures called from multiple threads, you must not increment the same
total
without adding some synchronization. You could add synchronization with a serial dispatch queue, for example:There are a variety of alternative synchronization mechanisms (locks, GCD reader-writer,
actor
, etc.). But I start with the serial queue example to observe that, actually, any serial queue would accomplish the same thing. Many use the main queue (which is a serial queue) for this sort of trivial synchronization where the performance impact is negligible, such as in this example.For example, one could therefore either refactor
getMySecondItem
to also call its completion handler on the main queue, likegetMyFirstItem
andgetMyThirdItem
already do. Or if you cannot do that, you could simply have thegetMySecondItem
caller dispatch the code that needs to be synchronized to the main queue:That is also thread-safe. This is why many libraries will ensure that all of their completion handlers are called on the main thread, as it minimizes the amount of time the app developer needs to manually synchronize values.
While I have illustrated the use of serial dispatch queues for synchronization, there are a multitude of alternatives. E.g., one might use locks or GCD reader-writer pattern.
The key is that one should never mutate a variable from multiple threads without some synchronization.
Above I mention that you need to know when the three asynchronous tasks are done. You can use a
DispatchGroup
, e.g.:And in this example, I abstracted the synchronization details out of
addUpValues
:Obviously, use whatever synchronization mechanism you want (e.g., GCD or
os_unfair_lock
or whatever).But the idea is that in the GCD world, dispatch groups can notify you when a series of asynchronous tasks are done.
I know that this was a GCD question, but for the sake of completeness, the Swift concurrency
async
–await
pattern renders much of this moot.Or, if your async methods were updating some shared property, you would use an
actor
to synchronize access. See Protect mutable state with Swift actors.