iosswiftmultithreadingswiftui

Why Swift always suspend when running async function even if there is no asynchronous code inside it


I have read some articles and have been told that if an async function only contains normal synchronous code, then there won't be a suspension during runtime, no switching thread, as this article mentioned

If a suspension does not happen, no pause will take place and your function will continue to run with the same efficiency and timings as a synchronous function. That last part carries an important side effect: using await will not cause your code to wait for one runloop to go by before continuing.

However I did a test and it seems doesn't work that way

struct ContentStructure: View {
    var body: some View {
        CustomView()
        .task {
            print("task start thread = \(Thread.current)")
            await fakeExpensiveWork()
            print("task end thread = \(Thread.current)")
        }
    }
}

func fakeExpensiveWork() async {
    print("fake expensive work thread = \(Thread.current)")
}

struct CustomView: View{
    
    init(){
        print("init thread = \(Thread.current)")
    }
    
    var body: some View{
        List{
            ForEach(1..<100){ _ in
                Text("Hello, world!")
            }
        }
    }
}

printed:

init thread = <_NSMainThread: 0x600001704000>{number = 1, name = main}
task start thread = <_NSMainThread: 0x600001704000>{number = 1, name = main}
fake expensive work thread = <NSThread: 0x60000176ae00>{number = 6, name = (null)}
task end thread = <_NSMainThread: 0x600001704000>{number = 1, name = main}

Compiled with: Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4) Target: arm64-apple-darwin23.6.0

As the log showed, it always switch a thread to do the fakeExpensiveWork() even if it just print a log. When I put Thread.sleep(forTimeInterval: 10) inside the fakeExpensiveWork, the List still can be scrolled smoothly.

So is my understanding of this article incorrect, or has Apple updated the behavior of Swift?


Solution

  • Because fakeExpensiveWork doesn't bound to any Actor so when it is called, your code is submit to global concurrent Excutor, then it will submit job to coorperative pool which is a group of thread managed so it depends on coorperative pool to assign your job to a particular thread, therefore we can not exactly know which thread your code is running ( a special case is MainActor when we know that our code is run on Main Thread). Take a look at this excellent blog for more explain