Skip to content

GCD – Beta

gcd

At WWDC 2016 Apple introduced some interesting additions to libdispatch, or more commonly known as Grand Central Dispatch (GCD). QoS was stressed quite a bit during the talk and the additions made to the GCD API enable us to address QoS problems and priority inversion more effectively. In this post I am going to share my initial thoughts to the concepts outlined by the GCD team and the changes introduced to the API.

An important concept has been how to properly structure your application when using queues. The more queues you use the harder it is to avoid priority inversion. It is important to choose the right number of queues in your application. In order to do so we need to identify areas of our application with independent data flows. Each of those areas can then become a subsystem in our application backed by its queue. Another important tip is to default to make those queues serial first, and only make them concurrent if really needed. A very good piece of advice found in the Performance on iOS and watchOS WWDC talk is “…Don’t guess… use the performance tools..“. So, before making things concurrent measure where the slow down is and be sure to have correct estimation of possible performance bottlenecks in your application. This allows us to avoid spawning too many threads due to blocking threads.

These subsystems can then work together and the suggested approach is to use chaining and grouping. These patterns are very well supported by GCD. We will look at some of what GCD offers to address these patterns later on.

As we submit work items to queues we need to prioritize work to best address the needs of our application. Quality of Service classes (QoS) allow us to have more control on prioritization.

Below I am going to outline some of the new APIs and provide some of my observations. I am not going to cover all there is to cover, but I am going to show snippets of code that should be commonly used to deal with asynchronous flows in iOS.

Dispatch to main, probably one of the most commonly used GCD APIs, often used to update the UI for example when not already on the main thread.

Used to be:

new API reads:

Much more readable! For sure one of my favorite changes simply because I don’t have to type as much and it looks a lot cleaner.

So, let’s look at queue creation:

Instantiating a serial queue is as simple as:

I could also use a different available initializer to instantiate a serial queue:

Using the attributes array we can also assign a specific QoS to the queue all in one line:

To instantiate a concurrent queue targeting the global default queue:

These are significant changes. In one line we can now create a queue and specify multiple attributes and even a possible target queue. We can also pass in a dispatch group when needed. Any of us that used GCD before the latest release can appreciate the new simplicity and functionality introduced. In the past, in order to create a queue with a specific QoS, we first had to create a dispatch_queue_attr_t. Now, that is just to take care of the QoS! As we can see in the above example we can specify many more attributes and target queue just in one line and in a much cleaner syntax!

DispatchWorkItem:

Now onto one of my favorite addition released, DispatchWorkItem. As we know GCD allows us to think in terms of queues and not really have to worry about handling Threads directly. This concept alone has made things a lot easier  compared to what we had before GCD. The reason why we have queues is so that we can then “dispatch” a unit of work on queues. A serial queue will execute dispatched blocks following a strict FIFO order. Concurrent queues will execute dispatched blocks concurrently without guaranteeing any specific order.

So, lets take a look at DispatchWorkItem.

When I first looked at the a DispatchWorkItem I thought about the recent UIView animation changes they released. Why?

Well, I am referring to UIViewPropertyAnimator:

Both in UIKit and in GCD we now have more control through the returned object. In GCD we can now create a unit of work to dispatch on a queue by using a DispatchWorkItem and in UIKit we can now hold onto the UIViewPropertyAnimator and interact with it as needed. So, like Steve Breen mentioned during his talk at WWDC, having an object to interact with gives us a lot more leverage. Sure, he was referring to UIViewPropertyAnimator but the analogy we can draw with DispatchWorkItem is very straight forward.

Another change is creation-based-context rather than submission-based-context. When you async a closure to a dispatch queue it captures the execution context of the point where you asynced. What the DispatchWorkItem allows us to do now is to capture a certain context independently of when we actually async the item. This can be very useful when we need to capture context that we know won’t be around at dispatch time. To do this we can use the “.assignCurrentContext” flag.

QoS

Let’s take a look at how specifying QoS on a DispatchWorkItem affects the execution:

In the console we get this:

DispatchWorkItem was created by passing in the “.enforceQoS” flag. As the work was being prepared for execution to the queue that caused the QoS to prioritize highPriorityItem over most of the other items, in-fact from the console we can see how it came in second over the 10 dispatched items.
It is important to note that QoS classes do not allow a work item to jump items in the queue, it just allows the item to be prioritized as the work is being prepared to be dispatched to the queue. If we run this code multiple times we see how the log “START TASK. HIGH,QoSQoS = 33” does not always come in second, in-fact sometime it comes in first, sometime it comes in very low in priority, so order is not guaranteed.

So then, what happens if we take the enforceQoS flag off like so:

The console will print this:

We can learn two things here:
1. The highPriorityItem isn’t prioritized over the other items anymore, but, it’s dispatched last.
2. When highPriorityItem is executed on the queue, even though the highPriorityItem’s QoS was declared as userInteractive, it inherits the queue’s QoS qosDefault! The queue’s QoS enforces its QoS over dispatched work, unless an item enforces its QoS, we do so by passing in enforceQoS is used

Grouping

Now let’s look at a very simple example of the grouping pattern implementation using the new API:

the console shows the following:

One thing that didn’t quite work they way I thought it would is calling concurrentQ.async(execute: item) if the item was instantiated with a group. I thought it would automatically call the “enter” and “leave” but it does not and notify gets called before it is done executing:

That is why I ended up calling:

I suspect there might be some gotcha to this I have not seen yet, waiting for more documentation from Apple on this soon. (update: it seems like this is considered a mistake and the ability to pass in a group when instantiating a DispatchWorkItem will be removed. http://comments.gmane.org/gmane.comp.lang.swift.corelibs/781)

Synchronization

Let’s take a look at a simple synchronization example:

Console shows:

This is an example of how barriers can be used as a synchronization point on a concurrent queue.

cancel()

One of the reasons to chose NSOperation over GCD is if we have the need to be able to cancel the unit of work we need to perform and NSOperation allows us to cancel the work even during execution. As we know, an NSOperation can be cancelled before it even starts executing on an NSOperationQueue. Furthermore, an NSOperation can also be cancelled during its execution. I won’t go in details about how that works, but, as the NSOperation runs we can mark it as cancelled and then during its execution it should be implemented to check for cancellation state often so that cancellation can be handled promptly.
GCD still does not offer the ability to cancel closures that have already started executing on a certain queue. What we do have now though is the ability to mark a work item as cancelled. If the call cancel() happens before the work item is dispatched then the work item will not execute.

Let’s look at a quick simple example:

Console shows:

So we see that “Item not cancelled” did not run, item’s execution was cancelled.

wait()

Another addition is the wait() call on a DispatchWorkItem. We can either call wait(), or wait with a DispatchTime timeout. So what are the effects of calling wait() on a DispatchWorkItem? Let’s test things out:

Console shows:

Calling wait on the dispatchWorkItem blocks the execution on the thread wait() was called on. It is important to use wait() carefully in order to avoid deadlocks.

DispatchPredicate

DispatchPredicate is a very simple feature, but probably one of my favorites. Sometime we want to make sure that some logic is run on a a certain queue. Let’s say we want to serialize some functionality and we do so by dispatching work on a serial queue. Let’s say we write a function that must be called when already on the serial queue we created to address the serialization flow. We can enforce that by using DispatchPredicates.

Example:

The available conditions as of now are:
.onQueue
.onQueueAsBarrier
.notOnQueue

Conclusion

Overall the GCD changes introduced are a mix of new features and syntax changes. Both of them are good improvements. The GCD team has for sure worked on helping us effectively address priority inversion problems by allowing us to have finer control over QoS attributes, and the interesting part is that work items’s QoS can now execute independently of the queue’s QoS when they enforce their QoS. The relationship between item’s QoS and queue’s QoS is also interesting and it can be controlled by properly using item’s attributes and queue’s attributes. We now have better control on cancellation logic, closure context state and we can now enforce certain queue conditions using DispatchPredicates. In general, I would suggest that to properly adopt GCD in your code, one should understand priority inversion concepts well and thread explosion.
Keep in mind changes are still coming through. For example one change to look for is the one mentioned by Pierre Habouzit on removing the group argument when instantiating a DispatchWorkItem as it was a mistake.

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *