skip to Main Content

I have a SwiftUI application where I am animating a Text view and have two buttons to trigger a heavy load computation in the background. I am using both DispatchQueue and Task to perform this heavy computation, and I’m interested in understanding the differences and best practices for using these two methods in SwiftUI.

    import SwiftUI
    
    struct ContentView: View {
        @State private var scale: CGFloat = 1.0
        var body: some View {
            VStack {
                Text("Animating Text")
                    .font(.largeTitle)
                    .scaleEffect(scale)
                    .animation(
                        Animation.easeInOut(duration: 1.5)
                            .repeatForever(autoreverses: true)
                    )
                    .onAppear {
                        self.scale = 1.5
                    }
                Button("Run With DispatchQueue") {
                    DispatchQueue.global(qos: .background).async {
                        performHeavyLoad(duration: 60 * 5)
                    }
                }
                Button("Run With Task") {
                    Task(priority: .background) {
                        // Example: High-load computation on the main thread for 5 minutes
                        performHeavyLoad(duration: 60 * 5)
                    }
                }
            }
        }
        
        // Function to perform heavy computation on the calling thread
        nonisolated
        func performHeavyLoad(duration: TimeInterval) {
            let start = Date()
            let end = start.addingTimeInterval(duration)
    
            while Date() < end {
                for _ in 0..<1_000_000 {
                    _ = sqrt(12345.6789)
                }
            }
        }
    }

In this example, Button("Run With Task") will cause UI unresponsive, but Button("Run With DispatchQueue") will not.

2

Answers


    • Run With Task executes stuff on the main thread, so it’s the cause of unresponsive behavior.
    • Run With DispatchQueue on other hands, executes on the background thread as you declared.

    Simply print out the thread to check that:

    func performHeavyLoad(duration: TimeInterval) {
        print(Thread.current)
        ...
    }
    
    //<NSThread: 0x6000017049c0>{number = 2, name = (null)}    <- Run With DispatchQueue
    //<_NSMainThread: 0x600001708040>{number = 1, name = main} <- Run With Task
    

    It’s running on the main thread because the Task is inherited from its parent context, in this case, it’s the Main Actor.

    @ViewBuilder @MainActor var body: Self.Body { get }


    From my perspective, you should never mix DispatchQueue style with Async Await. You may want to re-write the function to:

    var body: some View {
        ...
        Button("Run With Task") {
            Task(priority: .background) {
                await performHeavyLoadWithAsync(duration: 60 * 5)
            }
        }
    }
    
    func performHeavyLoad(duration: TimeInterval) async {
       print(Thread.current)
       ...
    }
    
    //<NSThread: 0x6000017434c0>{number = 8, name = (null)}
    
    Login or Signup to reply.
  1. I think Task.detached {..} (even though it’s an undesirable technique here) will do the job.

    Button("Run With Task") {
        Task.detached(priority: .background) {
            performHeavyLoad(duration: 60 * 5)
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search