Home > Software design >  Swift Concurrency : Why Task is not executed on other background thread
Swift Concurrency : Why Task is not executed on other background thread

Time:09-30

I am trying to learn the swift concurrency but it brings in a lot of confusion. I understood that a Task {} is an asynchronous unit and will allow us to bridge the async function call from a synchronous context. And it is similar to DispatchQueue.Global() which in turn will execute the block on some arbitrary thread.

override func viewDidLoad() {
        super.viewDidLoad()
        
        Task {
            do {
                    
                 let data = try await asychronousApiCall()
                 print(data)
                    
                } catch {
                    print("Request failed with error: \(error)")
                }
        }
        
        for i in 1...30000 {
            print("Thread \(Thread.current)")
        }
    }

my asychronousApiCall function is below

func asychronousApiCall() async throws -> Data {
        print("starting with asychronousApiCall")
        print("Thread \(Thread.current)")
        let url = URL(string: "https://www.stackoverflow.com")!
        // Use the async variant of URLSession to fetch data
        // Code might suspend here
        let (data, _) = try await URLSession.shared.data(from: url)
        
        return data
    }

When I try this implementation. I always see that "starting with asychronousApiCall" is printed after the for loop is done and the thread is MainThread.

like this

Thread <_NSMainThread: 0x600000f10500>{number = 1, name = main}

CodePudding user response:

You said:

I understood that a Task {} is an asynchronous unit and will allow us to bridge the async function call from a synchronous context.

Yes.

You continue:

And it is similar to DispatchQueue.global() which in turn will execute the block on some arbitrary thread.

No, if you call it from the main actor, it is more akin to DispatchQueue.main.async { … }. As the figure 8 figure 8

In that screen snapshot, they are showing that prior to the suspension point (i.e., before the await) it was on the main thread (which makes sense, because it is running it on behalf of the same actor). But they are also highlighting that after the suspension point, it was on another thread (which might seem counterintuitive, but it is what can happen after a suspension point). This is very common behavior in Swift concurrency, though it can vary.

FWIW, in your example above, you only examine the thread before the suspension point and not after. The take-home message of figure 8 is that the thread used after the suspension point may not be the same one used before the suspension point.

If you are interested in learning more about some of these implementation details, I might suggest watching WWDC 2021 video Swift concurrency: Behind the scenes.


While it is interesting to look at Thread.current, it should be noted that Apple is trying to wean us off of this practice. E.g., in Swift 5.7, if we look at Thread.current from an asynchronous context, we get a warning:

Class property 'current' is unavailable from asynchronous contexts; Thread.current cannot be used from async contexts.; this is an error in Swift 6

The whole idea of Swift concurrency is that we stop thinking in terms of threads and we instead let Swift concurrency choose the appropriate thread on our behalf (which cleverly avoids costly context switches where it can; sometimes resulting code that runs on threads other than what we might otherwise expect).

  • Related