1

I found code example of parallel execution from this site:

In contrast, if you change both Task initializers to Task.detached, you’ll see “In Task 1” and “In Task 2” get intermingled as both execute at the same time.

and adapt it to async let calls, but for some reason this code is not executed in parallel... enter image description here

struct ContentView: View {
    var body: some View {
        Task {
            async let result =  runForLoop1()
            async let result2 =  runForLoop2()
        }
        
        return Text("Hello, world!")
    }
}

func runForLoop1() async -> Int {
    for i in 1...1000 {
        print("In Task 1: \(i)")
    }
    return 100_000
}

func runForLoop2() async -> Int {
    for i in 1...1000 {
        print("In Task 2: \(i)")
    }
    return 100_000
}

I use Xcode Version 13.2.1 (13C100)

Edit

I also try to run @Rob example on Mac Console app, but this app crush:

enter image description here

Task {
    async let result =  runForLoop1()
    async let result2 =  runForLoop2()
}

func runForLoop1() async -> Int {
    var i = 0
    let start = Date()
    while Date().timeIntervalSince(start) < 1 {
        print("In Task 1: \(i)")
        i += 1
    }
    return 100_000
}

func runForLoop2() async -> Int {
    var i = 0
    let start = Date()
    while Date().timeIntervalSince(start) < 1 {
        print("In Task 2: \(i)")
        i += 1
    }
    return 100_000
}

Edit 2

I also try to run @Rob example on macOS standard app, but this app crush:

enter image description here

Code:

struct ContentView: View {
    var body: some View {
        
        Task {
            async let result =  runForLoop1()
            async let result2 =  runForLoop2()
            await print(result, result2)
        }
        
        return Text("Hello, world!")
            .padding()
    }
}

func runForLoop1() async -> Int {
    var i = 0
    let start = Date()
    while Date().timeIntervalSince(start) < 1 {
        print("In Task 1: \(i)")
        i += 1
    }
    return 100_000
}

func runForLoop2() async -> Int {
    var i = 0
    let start = Date()
    while Date().timeIntervalSince(start) < 1 {
        print("In Task 2: \(i)")
        i += 1
    }
    return 100_000
}

  • You don't have any async code in `runForLoop1()` or `runForLoop2()`. And, since you don't launch them in separate tasks, as the link you referenced does, they just run on the same context you're already in. – jnpdx May 12 '23 at 15:12
  • @jnpdx can you provide an example of parallel execution that I could run? I'm a little confused what other code I should have added? My functions are asynchronous so they must be executed on separate threads. – MaxFactorHub May 12 '23 at 15:18
  • Async await doesn’t deal with threads it deals with actors adding the async decorator doesn’t mean that they will run in a different actor – lorem ipsum May 12 '23 at 15:40
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/253632/discussion-between-rob-and-maxfactorhub). – Rob May 12 '23 at 16:40

1 Answers1

1

The short answer is that async let does allow concurrent execution.

If you are not seeing parallel execution, there are any of a number of issues.

  1. There might not be enough iterations to manifest concurrency.

    You could change this so that they take a certain amount of time and you will see the parallel execution. Or, easier, use Task.sleep:

    func runForLoop1() async -> Int {
        print(#function, "start")
        try? await Task.sleep(for: .seconds(1)) // or, in your old version of Xcode, `Task.sleep(nanoseconds: 1_000_000_000)`
        print(#function, "end")
    
        return 100_000
    }
    
    func runForLoop2() async -> Int {
        print(#function, "start")
        try? await Task.sleep(for: .seconds(1))
        print(#function, "end")
    
        return 100_000
    }
    
  2. Also, make sure to avoid using the simulator in Xcode versions prior to 14.3. See Maximum number of threads with async-await task groups.

    As an aside, I would also would avoid using playgrounds, for parallelism tests. Make sure to run this as an app, on a physical device.

  3. In your code sample, you are initiating the async methods, but are not awaiting their results. I would await the printing of the results:

    struct ContentView: View {
        var body: some View {
            Text("Hello, world!")
                .task {
                    async let result = runForLoop1()
                    async let result2 = runForLoop2()
    
                    await print(result, result2)
                }
        }
    }
    

    Note, the standard pattern for initiating a task in a View would be the .task view modifier:

    Or, in older versions of Xcode:

    struct ContentView: View {
        var body: some View {
            Text("Hello, world!")
                .onAppear {
                    Task {
                        async let result = runForLoop1()
                        async let result2 = runForLoop2()
    
                        await print(result, result2)
                    }
                }
        }
    }
    
  4. If your methods are defined in an actor-isolated context, make sure to designate them as nonisolated or else you will not enjoy parallelism.

    Note, SE-0338 formalizes “where asynchronous functions that aren't actor-isolated run.” That having been said, that was implemented in Swift 5.7, so I hesitate to get too specific re Xcode 13.2.1.

  5. As an aside, it is a little strange to mark synchronous methods (i.e., there is no await inside these functions) as async, when they are not. E.g., for two synchronous functions that I want to run in the background in parallel, I would remove async keyword and might use two detached tasks:

    struct ContentView: View {
        var body: some View {
            Text("Hello, world!")
                .onAppear {
                    Task {
                        async let result = Task.detached { runForLoop1() }.value
                        async let result2 = Task.detached { runForLoop2() }.value
    
                        await print(result, result2)
                    }
                }
        }
    
        nonisolated func runForLoop1() -> Int {
            print(#function, "start")
            Thread.sleep(forTimeInterval: 1)
            print(#function, "end")
            return 100_000
        }
    
        nonisolated func runForLoop2() -> Int {
            print(#function, "start")
            Thread.sleep(forTimeInterval: 1)
            print(#function, "end")
            return 100_000
        }
    }
    

    Needless to say, you would never Thread.sleep in your actual project, but just to illustrate the pattern.

  6. Offline, we discussed Xcode and OS versions. You apparently are running Xcode 13.2.1. While Xcode 13 launched without Swift concurrency support for older OS versions, but subsequent versions added this. E.g., in my experience, Swift concurrency support was flaky as of Xcode 13.3 beta, but it allegedly has stabilized a bit. So I would advise upgrading to a more contemporary Xcode version (though I gather you are constrained by hardware running Big Sur).

Rob
  • 415,655
  • 72
  • 787
  • 1,044