Why Concurrency?
As soon as you add heavy tasks to your app like data loading it slows your UI work down or even freezes it.
Concurrency lets you perform 2 or more tasks “simultaneously”.
The disadvantage of this approach is that thread safety which is not always as easy to control. F.e. when different tasks want to access the same resources like trying to change the same variable on a different threads or accessing the resources already blocked by the different threads.
There are a few abstractions we need to be aware of.
- Queues.
- Synchronous/Asynchronous task performance.
- Priorities.
- Common troubles.
Queues
Must be serial or concurrent. As well as global or private at the same time.
With serial queues, tasks will be finished one by one while with concurrent queues, tasks will be performed simultaneously and will be finished on unexpected schedules. The same group of tasks will take the way more time on a serial queue compared to a concurrent queue.
You can create your own private queues (both serial or concurrent) or use already available global (system) queues.
The main queue is the only serial queue out of all of the global queues.
It is highly recommended to not perform heavy tasks which are not referred to UI work on the main queue (f.e. loading data from the network), but instead to do them on the other queues to keep the UI unfrozen and responsive to the user actions. If we let the UI be changed on the other queues, the changes can be made on a different and unexpected schedule and speed. Some UI elements can be drawn before or after they are needed. It can crash the UI. We also need to keep in mind that since the global queues are system queues there are some other tasks can run by the system on them.
Quality of Service / Priority
Queues also have different qos (Quality of Service) which sets the task performing priority (from highest to lowest here):
.userInteractive – as for the main queue
.userInitiated – for the user initiated tasks on which user waits for some response
.utility – for the tasks which takes some time and doesn’t require immediate response, e.g working with data
.background – for the tasks which aren’t related with the visual part and which aren’t strict for the completion time).
There is also
.default queue which does’t transfer the qos information.
If it wasn’t possible to detect the qos the qos will be used between .userInitiated and .utility.
Tasks can be performed synchronously or asynchronously.
-
Synchronous function returns control to the current queue only after the task is finished. It blocks the queue and waits until the task is finished.
-
Asynchronous function returns control to the current queue right after task has been sent to be performed on the different queue. It doesn’t wait until the task is finished. It doesn’t block the queue.
Common Troubles.
The most popular mistakes programmers make while projecting the concurrent apps are the following:
- Race condition – caused when the app work depends on the order of the code parts execution.
- Priority inversion – when the higher priority tasks wait for the smaller priority tasks to be finished due to some resources being blocked
- Deadlock – when a few queues have infinite wait for the sources (variables, data etc.) already blocked by some of these queues.
NEVER call the sync function on the main queue.
If you call the sync function on the main queue it will block the queue as well as the queue will be waiting for the task to be completed but the task will never be finished since it will not be even able to start due to the queue is already blocked. It is called deadlock.
When to use sync?
When we need to wait until the task is finished. F.e. when we are making sure that some function/method is not double called. F.e. we have synchronization and trying to prevent it to be double called until it’s completely finished. Here’s some code for this concern:
How to find out what caused error crash report on IOS device?