Leave a rating/review
Grand Central Dispatch queues manage threads for you. The system provides several global queues that you can use.
The main queue is used so often for UI updates, it’s a special type property DispatchQueue.main. The main queue is serial: It runs on only one thread, the main thread.
The other global queues are concurrent: They can have more than one thread. Each queue runs at a different priority level, or quality of service. Dispatching to a global queue specifies the purpose of a task, and lets the scheduler decide how to prioritize the different threads.
You specify a concurrent global queue with the type method DispatchQueue.global and a Quality of Service qos enumeration value. The list here goes from high to low quality of service.
You’ll rarely specify user Interactive explicitly: It’s for tasks that must be done practically as fast as the main queue. The user is dragging or pinching, and the app must do something right away, off the main queue, but return to the main queue during the touch gesture.
You’ll mostly use the next 3 qos levels:
user Initiated is for tasks initiated from the UI, like tapping a button. The user needs it done immediately, but it can be done off the main queue, like opening a saved document.
utility is for long-running tasks with progress indicators, like computations, I/O, networking, continuous data feeds. The system tries to balance responsiveness & performance with energy efficiency.
And background is for tasks the user is not directly aware of. They don’t require user interaction, aren’t time-sensitive, and often involve network activity like prefetching, database maintenance, synchronizing with remote servers, or backups. The system aims for energy efficiency.
If you don’t specify a qos value, you get the default queue: It’s for work that has no explicit or inferred quality of service.
unspecified supports legacy APIs that may opt the thread out of a quality of service. And it’s the default value when creating dispatch work items and dispatch groups.
You can create your own private queues using the DispatchQueue initializer, providing a label and optional qos or attributes.
A private queue is serial by default, because serial queues are handy for protecting shared resources.
To create a private concurrent queue, use the concurrent attribute. This can be helpful for debugging, and you need one to implement a dispatch barrier, as you’ll see in Part 2.
If you don’t specify a qos value, the system infers it from the current context when a task is created or dispatched. If you dispatch a task from the main queue, the inferred qos is user Initiated.
Even if you specify a qos value for a private queue, like utility here, the system will increase it if you add a higher qos task to the queue. For example, if you add a task from the main queue to this utility queue, its qos will go up to user initiated. Any future tasks will also have their qos promoted. This is to solve the priority inversion problem you’ll learn about in Part 2.
If you just need to run an occasional non-UI task, dispatch it to the global queue with the appropriate qos level. For larger, more complex apps, analyze the data interactions to identify subsystems like these. Then create private dispatch queues for each subsystem.
A major purpose of concurrency programming is to run non-UI tasks off the main queue. The destination queue can be serial, with 1 thread, or concurrent, with more than 1 thread if needed. If you run the non-UI tasks on a serial queue, only one at a time can run.
The later task has to wait for the first to finish. You can solve many concurrency problems by using a serial queue. Because only one task at a time can be executed, it provides mutual exclusion more safely than directly using thread locks.
If you run the non-UI tasks on a concurrent queue, it creates a new thread to run the new task immediately. You’ve dispatched all of these non-UI tasks asynchronously from the main queue, so the UI tasks continue to run as they arrive.
Concurrent is not the same thing as asynchronous. You can dispatch a task asynchronously or synchronously from the current queue, to a serial or concurrent queue.
When you dispatch a task to another queue, that queue might be serial or it might be concurrent. Serial vs concurrent affects the task you dispatched: Will it have to wait its turn in a serial queue, or will the concurrent queue run it on another thread?
When you dispatch a task from the current queue, you can dispatch it synchronously or asynchronously. This affects tasks on the current (source) queue: If you dispatch synchronously, these tasks must wait until the dispatched task returns. If you dispatch asynchronously, the source queue runs its tasks normally.
Here’s the main queue.
If you dispatch a task asynchronously, the current queue can continue immediately with the next task.
If you dispatch a task synchronously from a serial queue like main, it must wait until the task completes before it can do anything else. This is useful if the next task accesses the same resource, to make sure only one task at a time can change that resource. But it’s a bad user experience if the current queue is the main queue: The UI stops responding.
If you dispatch a task synchronously from a concurrent queue, it blocks that thread, but a concurrent queue can continue working on tasks on its other threads; if another task arrives, it just creates another thread for that task, or queues it onto an existing thread.
Synchronous vs asynchronous tells you whether the current queue has to wait for the task to complete. Serial vs concurrent just tells you whether a queue has one or many threads, that is, whether it can run only one or many tasks simultaneously.
The most common use of dispatch queues is dispatching a non-UI task asynchronously to a concurrent queue.
and dispatching a UI task asynchronously back to the main queue.
Other common use cases dispatch to a serial queue:
To read a shared resource value, dispatch synchronously to wait for the value: the next task probably uses that value, and you don’t want it to run unless it has the current value.
To write a shared resource value, dispatch asynchronously because writing is something you do when you’re finished working with a value.
A serial queue can be too restrictive for multiple read tasks, which could run at the same time, if there’s no risk of a write task sneaking in to change the value. In part 2, you’ll learn how to use a dispatch barrier to do exactly this.
The quickest way to add tasks to a queue uses its async or sync method, providing the task in a closure.
Use the async method to move slow tasks off the main queue and to update the UI on the main queue.
Use the sync method to stop the current thread until a short but critical task completes on another queue, usually setting a value.
When using sync, be careful not to create a cycle: If the task you’re dispatching needs the current queue to do something, then your app will deadlock.
In the next video, you’ll practice dispatching tasks in an Xcode playground.