Concurrency Concepts Flashcards
iOS Concurrency:
What possibilities do you have to run code on a background thread in iOS?
The first possibility is to use the old plain threads, but by using them directly, you would have to deal with a low-level API.
A higher level of abstraction is provided by Grand Central Dispatch (GCD). When using GCD, you can execute blocks of code on a DispatchQueue - either one you create by yourself or one provided by the iOS system.
Another possiblity is to use Operations which are built on top of GCD.
iOS Concurrency:
What is the difference between a serial and a concurrent queue?
Serial queues execute one task at a time in the order in which they are added to the queue. So a serial queue guarantees that a task will finish first before another task will start. Concurrent queues allow multiple tasks to run at the same time. The code below shows how these two types of queues are created.
let serialQueue = DispatchQueue(label: "serialQueue") let concurrentQueue = DispatchQueue(label: "concurrentQueue", attributes: .concurrent)
iOS Concurrency:
When you review the code below, would you suggest an improvement?
DispatchQueue.global(qos: .background).async { let text = loadDescription() label.text = text }
Yes. UIKit can only be used from our app’s main queue, so the label’s text shouldn’t be set on a background queue. One possible way to solve this is to call the UI update code on the main queue.
DispatchQueue.global(qos: .background).async { let text = loadDescription()
DispatchQueue.main.async { label.text = text } }
iOS Concurrency:
What is the difference between asynchronous and synchronous tasks?
Synchronously starting a task blocks the calling thread until the task is finished. Asynchronously starting a task returns directly on the calling thread without blocking.
iOS Concurrency:
How can you cancel a running asynchronous task?
In GCD, you can achieve that with DispatchWorkItem. By setting it up with the task that needs to be done asynchroniously and calling the cancel method when needed.
let workItem = DispatchWorkItem { [weak self] in // Execute time consuming code. }
// Start the task. DispatchQueue.main.async(execute: workItem)
// Cancel the task. workItem.cancel()
By using Operations, it’s almost the same as with GCD. You can use the cancel method on the operation.
let queue = OperationQueue() let blockOperation = BlockOperation { // Execute time consuming code. }
// Start the operation. queue.addOperation(blockOperation)
// Cancel the operation. blockOperation.cancel()
iOS Concurrency:
How can you group asynchronous tasks?
By using GCD, you can use DispatchGroup to achieve this.
let group = DispatchGroup()
// The 'enter' method increments the group's task count. group.enter() dataSource1.load { result in // Save the result. group.leave() }
group.enter() dataSource2.load { result in // Save the result. group.leave() }
// This closure is called when the group's task count reaches 0. group.notify(queue: .main) { [weak self] in // Show the loaded results. }
When working with operations, you can add dependencies between them.
let operation1 = BlockOperation { // Execute time consuming code. } let operation2 = BlockOperation { // Execute time consuming code. } let operation3 = BlockOperation { // operation1 and operation2 are finished. } operation3.addDependency(operation1) operation3.addDependency(operation2)
iOS Concurrency:
What is a race condition?
A race condition can occur when multiple threads access the same data without synchronization and at least one access is a write. For example if you read values from an array from the main thread while a background thread is adding new values to that same array.
Data races can be the root cause behind unpredicatable program behaviours and weird crashes that are hard to find.
iOS Concurrency:
How can you avoid race conditions?
To avoid race conditions you can access the data on the same serial queue or you can use a concurrent queue with a barrier flag. A barrier flag makes access to the data thread-safe.
var queue = DispatchQueue(label: “messages.queue”, attributes: .concurrent)
queue.sync { // Code for accessing data }
queue.sync(flags: .barrier) { // Code for writing data }
The barrier flag approach has the benefit of synchronizing write access while reading can still be done concurrently. By using a barrier, a concurrent queue becomes a serial queue for a moment. A task with a barrier is delayed until all running tasks are finished. When the last task is finished, the queue executes the barrier block and continues it’s concurrent behavior after that.
iOS Concurrency:
How can you find and debug race conditions?
You can use Apples Thread Sanitizer to debug race conditions, it detects them at runtime. The Thread Sanitizer can be enabled from the scheme configuration.