Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Performance of background queue in swift?

Following up on my question here.

I have a method lotsOfWork() that may take some time to complete. While it runs, the user needs to wait for it to complete. I'd like to provide feedback to the user, so that (s)he sees what is going on.

I now have the following code to run my lotsOfWork() method, while allowing it to update a label showing its progress:

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var label: UILabel!

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        DispatchQueue.global(qos: .background).async {
            self.lotsOfWork()
        }
    }

    func lotsOfWork() {
        for i in 1...10 {
            DispatchQueue.main.async {
                self.label.text = "Working on item \(i)..."
            }
            sleep(1) // Simulating lots of work
        }
    }
}

But this also means the lotsOfWork() method will be executed in the background queue and not in main.

While lotsOfWork() is running, nothing really happens in the main queue - except from updating the label. The user cannot do anything but wait.

Question 1: Is this a substantial performance issue? Will the work need more time to complete?

Question 2: If this is an issue, is there a way to let the lotsOfWork() method run in main, while still being able to update the label.text?

I tried to use DispatchQueue.main.async and even 2 nested DispatchQueue.main.async, but this does not update the label.text.

I also tried using setNeedsDisplay() and setNeedsLayout(), but this does not change anything.

It would not be an issue to execute lotsOfWork() in main, because the user needs to wait for this work to complete before continuing. But I can't get the label.text to be updated in real time if lotsOfWork() runs in main.

like image 906
Arjen Hiemstra Avatar asked Mar 05 '23 03:03

Arjen Hiemstra


2 Answers

Your requested QoS (quality of service) is incorrect. You've requested background:

Used for work that is not user initiated or visible. In general, a user is unaware that this work is even happening. For example, pre-fetching content, search indexing, backups, or syncing of data with external systems.

Background tasks are the lowest priority tasks, and in principle might never be executed (at a minimum, you should be willing to accept delays of hours or more).

If the user requested the operation and must wait for it to complete, then then correct QoS is .userInitiated:

Used for performing work that has been explicitly requested by the user, and for which results must be immediately presented in order to allow for further user interaction. For example, loading an email after a user has selected it in a message list.

This QoS level can be (and often is) as performant as the main queue, though you should in general avoid seconding-guessing the system about what it will do and just make sure you tag operations with the QoS that matches the intent. The best introduction to all of this is Concurrent Programming with GCD in Swift 3.

I generally find that when people choose .background (the lowest QoS), they usually meant .utility, and when they choose .userInteractive (the highest level), they usually meant .userInitiated. The highest and lowest levels have very special use cases that don't come up very often. If you need the result in less than a day, you don't mean .background, and if it takes more than 16ms to complete, you don't mean .userInteractive.

like image 197
Rob Napier Avatar answered Mar 16 '23 04:03

Rob Napier


  1. According to Apple documentation found here:

Because higher priority work is performed more quickly and with more resources than lower priority work, it typically requires more energy than lower priority work. Accurately specifying appropriate QoS classes for the work your app performs ensures that your app is responsive and energy efficient.

The answer is yes. Lower priority queue may get less resources, therefore may have slower execution.

  1. The answer is no. You cannot perform relatively heavy work on main without blocking UI updates.

If you're interested in learning Concurrency in Swift, I suggest you to read this post creates by Umberto Raimondi. This is the best Swift concurrency guide I've seen so far.

like image 21
fewlinesofcode Avatar answered Mar 16 '23 05:03

fewlinesofcode