0

I have a background task that has several slow steps to be processed in sequence. I am trying to understand any difference between the following two approaches in Swift.

First approach:

import SwiftUI

struct ContentView: View {
    var body: some View {
        Button("Start Slow Task") {
            Task.detached(priority: .background) {
                await slowBackgroundTask()
            }
        }
    }
}


func slowBackgroundTask() async {
    slowStep1()
    slowStep2()
    slowStep3()
}

func slowStep1() {
    // takes a long time
}

func slowStep2() {
    // takes a long time
}

func slowStep3() {
    // takes a long time
}

Approach 2 is the same ContentView, but with the functions changed as follows.

func slowBackgroundTask() async {
    await slowStep1()
    await slowStep2()
    await slowStep3()
}

func slowStep1() async {
    // takes a long time
}

func slowStep2() async {
    // takes a long time
}

func slowStep3() async {
    // takes a long time
}


Is there any difference between these two patterns? I would be most grateful to understand this better.

Both versions build and run. I am trying to understand the difference.

Ian
  • 29
  • 4
  • Thanks. I had thought that in the second one the three functions would run one after the other due to the await keyword. – Ian Feb 16 '23 at 04:29
  • 1
    second function doesn't mean these subtasks are run concurrently, instead, slowStep2 will run once slowStep1 finishes, slowStep3 will run once slowStep2 finishes, that's because of `await`, but if you change them to `async let _ = slowStep1()`, those functions won't depend on each other. See https://www.avanderlee.com/swift/async-let-asynchronous-functions-in-parallel/ for more details – where_am_i Feb 16 '23 at 08:18

1 Answers1

1

Regarding the difference between these two patterns, they both will run the three slow steps sequentially. There are some very modest differences between the two, but likely nothing observable. I’d lean towards the first snippet, as there are fewer switches between tasks, but it almost certainly doesn’t matter too much.


FWIW, one would generally only mark a function as async if it is actually is asynchronous, i.e., if it has an await suspension point somewhere inside it. As The Swift Programming Language says,

An asynchronous function or asynchronous method is a special kind of function or method that can be suspended while it’s partway through execution.

But merely adding async qualifiers to synchronous functions has no material impact on their underlying synchronous behavior.

Now, if you are looking for a real performance improvement, the question is whether you might benefit from parallel execution (which neither of the snippets in the question can achieve). It depends upon a number of considerations:

  1. Are the subtasks actually independent of each other? Or is subtask 1 dependent upon the results of subtask 2? Etc.

  2. Are these subtasks trying to interact some some shared resource? Or is there going to be resource contention as you synchronize interaction with that shared resource?

  3. Is there actually enough work being done in each subtask to justify the (admittedly very modest) overhead of parallel execution? If the subtasks are not sufficiently computationally intensive, introducing parallelism can actually make it slower.

But, if answering the above questions, you conclude that you do want to attempt parallelism, that begs the question as to how you might do that. You could use async let. Or you could use a “task group”. Or you could just make three independent detached tasks. There are a number of ways of tackling it. We would probably want to know more about these three subtasks to advise you further.


As a final, preemptive, observation as you consider parallel execution, please note that the iOS simulators suffer from artificially constrained “cooperative thread pools”. E.g., see Maximum number of threads with async-await task groups. In short, when testing parallel execution in Swift concurrency, it is best to test on a physical device, not a simulator.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thank you so much of that comprehensive response and extra information. It was just what I needed. As it happens the steps are sequential but the work done inside the steps could be made to be parallel because they are processing elements of a collection that don't depend on each other. – Ian Feb 16 '23 at 21:04