RSS

DispatchQueue QoS Order Explained Best Practices & Common Pitfalls to Avoid

DispatchQueue QoS Order Explained Best Practices & Common Pitfalls to Avoid.

DispatchQueue, Qos, DispatchGroup Best Practices & Common Pitfalls to Avoid, examples / Sample code.

DispatchQueue QoS Order

In Swift, DispatchQueue allows you to specify the Quality of Service (QoS) for the tasks you dispatch. The QoS classes determine the priority of the tasks and how they are scheduled by the system. Here is the order of QoS classes from highest to lowest priority:

  1. userInteractive
  2. userInitiated
  3. default
  4. utility
  5. background
  6. unspecified

Explanation of DispatchQueue QoS Classes

DispatchQueue depends on the nature and priority of the task you are performing. Here are the general recommendations for each QoS class:

  • userInteractive: For tasks that need to be done immediately to provide a smooth user experience, such as animations or event handling. These tasks should complete in a few milliseconds.
  • userInitiated: For tasks that the user has initiated and is waiting for immediate results, such as opening a document or performing a calculation. These tasks should complete in a few seconds.
  • default: The default QoS class for tasks that do not have a specific priority. Use this for tasks that do not fall into any other category.
  • utility: For tasks that do not require immediate results and can run in the background, such as downloading files or processing data. These tasks can take a few seconds to minutes.
  • background: For tasks that are not visible to the user and can run in the background, such as syncing data or performing maintenance tasks. These tasks can take minutes to hours.
  • unspecified: For tasks that do not have a specified QoS class. Use this for tasks that do not have a specified QoS class. This is rarely used and generally not recommended unless you have a specific reason.

Example Usage of DispatchQueue QoS Classes

Here’s an example of how to create DispatchQueue instances with different QoS classes:

import Foundation

let userInteractiveQueue = DispatchQueue.global(qos: .userInteractive)
let userInitiatedQueue = DispatchQueue.global(qos: .userInitiated)
let defaultQueue = DispatchQueue.global(qos: .default)
let utilityQueue = DispatchQueue.global(qos: .utility)
let backgroundQueue = DispatchQueue.global(qos: .background)

// Example of dispatching tasks with different QoS
userInteractiveQueue.async {
    print("User Interactive Task")
}

userInitiatedQueue.async {
    print("User Initiated Task")
}

defaultQueue.async {
    print("Default Task")
}

utilityQueue.async {
    print("Utility Task")
}

backgroundQueue.async {
    print("Background Task")
}

In this example, tasks are dispatched to different global queues with specified QoS classes. The system will prioritize the execution of these tasks based on their QoS. Choose the appropriate QoS class based on the urgency and importance of the task to ensure optimal performance and user experience.

Dispatch queue starvation

Dispatch queue starvation occurs when higher-priority tasks continuously occupy the CPU, preventing lower-priority tasks from executing. This can lead to a situation where lower-priority tasks are “starved” of CPU time.

To avoid dispatch queue starvation, you can:

  • Balance Task Priorities: Ensure that not all tasks are assigned the highest priority. Use appropriate QoS classes based on the task’s urgency and importance.
  • Limit Concurrent Tasks: Control the number of concurrent tasks to prevent overwhelming the system.
  • Use Serial Queues: For tasks that can be executed sequentially, use serial queues to avoid contention. Here’s an example demonstrating how to balance task priorities and use serial queues to avoid starvation:

Here’s an example demonstrating how to balance task priorities and use serial queues to avoid starvation:

import Foundation

let highPriorityQueue = DispatchQueue.global(qos: .userInitiated)
let lowPriorityQueue = DispatchQueue.global(qos: .utility)
let serialQueue = DispatchQueue(label: "com.example.serialQueue")

// High priority task
highPriorityQueue.async {
    for i in 1...5 {
        print("High Priority Task \(i)")
        sleep(1) // Simulate work
    }
}

// Low priority task
lowPriorityQueue.async {
    for i in 1...5 {
        print("Low Priority Task \(i)")
        sleep(1) // Simulate work
    }
}

// Serial queue task
serialQueue.async {
    for i in 1...5 {
        print("Serial Queue Task \(i)")
        sleep(1) // Simulate work
    }
}

In this example:

  • highPriorityQueue is used for high-priority tasks.
  • lowPriorityQueue is used for low-priority tasks.
  • serialQueue ensures tasks are executed sequentially, preventing contention.

By balancing task priorities and using serial queues where appropriate, you can help prevent dispatch queue starvation and ensure a smoother execution of tasks.

Best Practices for DispatchQueue

1. Use Appropriate QoS Classes

Assign the correct Quality of Service (QoS) class based on the task’s priority and urgency:

  • userInteractive: For tasks that need to be done immediately to provide a smooth user experience.
  • userInitiated: For tasks initiated by the user that require immediate results.
  • utility: For long-running tasks that do not need immediate results.
  • background: For tasks that can run in the background without affecting the user experience.

2. Avoid Blocking the Main Thread

The main thread should remain responsive to user interactions. Avoid performing long-running or blocking operations on the main thread.

3. Use Serial Queues for Sequential Tasks

Use serial queues to ensure tasks are executed sequentially, which can help prevent race conditions and data corruption.

4. Limit the Number of Concurrent Tasks

Avoid overwhelming the system by limiting the number of concurrent tasks. Use a serial queue or a semaphore to control the concurrency level.

5. Use async for Non-blocking Operations

Use async to dispatch tasks that do not need to block the current thread.

6. Clean Up Resources

Ensure that resources are properly cleaned up after tasks are completed to avoid memory leaks.

7. Use DispatchGroup for Synchronization

Use DispatchGroup to synchronize multiple tasks and wait for their completion.

Example Code

Here’s an example demonstrating some of these best practices:

import Foundation

let highPriorityQueue = DispatchQueue.global(qos: .userInitiated)
let lowPriorityQueue = DispatchQueue.global(qos: .utility)
let serialQueue = DispatchQueue(label: "com.example.serialQueue")
let dispatchGroup = DispatchGroup()

// High priority task
highPriorityQueue.async(group: dispatchGroup) {
    for i in 1...5 {
        print("High Priority Task \(i)")
        sleep(1) // Simulate work
    }
}

// Low priority task
lowPriorityQueue.async(group: dispatchGroup) {
    for i in 1...5 {
        print("Low Priority Task \(i)")
        sleep(1) // Simulate work
    }
}

// Serial queue task
serialQueue.async(group: dispatchGroup) {
    for i in 1...5 {
        print("Serial Queue Task \(i)")
        sleep(1) // Simulate work
    }
}

// Notify when all tasks are complete
dispatchGroup.notify(queue: DispatchQueue.main) {
    print("All tasks are complete")
}

Common Pitfalls to Avoid When Using DispatchQueue in Swift

1. Blocking the Main Thread

Avoid performing long-running or blocking operations on the main thread, as it can make your app unresponsive. Always dispatch such tasks to a background queue.

2. Overusing Global Queues

While global concurrent queues are convenient, overusing them can lead to resource contention and performance issues. Use custom serial or concurrent queues when appropriate.

3. Not Using QoS Appropriately

Failing to assign the correct Quality of Service (QoS) class can lead to suboptimal performance. Ensure tasks are assigned the appropriate QoS based on their priority and urgency.

4. Ignoring Thread Safety

Accessing shared resources from multiple threads without proper synchronization can lead to race conditions and data corruption. Use synchronization mechanisms like serial queues or locks.

5. Creating Too Many Queues

Creating an excessive number of queues can lead to resource exhaustion and performance degradation. Reuse existing queues when possible.

6. Forgetting to Clean Up Resources

Ensure that resources are properly cleaned up after tasks are completed to avoid memory leaks and other resource management issues.

7. Not Using DispatchGroup for Synchronization

When multiple tasks need to be synchronized, failing to use DispatchGroup can lead to complex and error-prone code. Use DispatchGroup to manage task synchronization effectively.

Using DispatchGroup in Swift

DispatchGroup allows you to group multiple tasks together and be notified when they have all completed. This is useful for synchronizing tasks and ensuring that certain code runs only after a set of tasks has finished.

Example Code

Here’s an example demonstrating how to use DispatchGroup:

import Foundation

let dispatchGroup = DispatchGroup()
let highPriorityQueue = DispatchQueue.global(qos: .userInitiated)
let lowPriorityQueue = DispatchQueue.global(qos: .utility)

// High priority task
dispatchGroup.enter()
highPriorityQueue.async {
    for i in 1...5 {
        print("High Priority Task \(i)")
        sleep(1) // Simulate work
    }
    dispatchGroup.leave()
}

// Low priority task
dispatchGroup.enter()
lowPriorityQueue.async {
    for i in 1...5 {
        print("Low Priority Task \(i)")
        sleep(1) // Simulate work
    }
    dispatchGroup.leave()
}

// Notify when all tasks are complete
dispatchGroup.notify(queue: DispatchQueue.main) {
    print("All tasks are complete")
}

// Keep the main thread alive to see the output
RunLoop.main.run(until: Date(timeIntervalSinceNow: 10))

Happy concurrency coding.

References

OmniLock - Block / Hide App on iOS

Block distractive apps from appearing on the Home Screen and App Library, enhance your focus and reduce screen time.

DNS Firewall for iOS and Mac OS

Encrypted your DNS to protect your privacy and firewall to block phishing, malicious domains, block ads in all browsers and apps

Ad