Home > Net >  How to call async function asynchronously without awaiting for the result
How to call async function asynchronously without awaiting for the result

Time:11-05

Let's say I have the following functions.

func first() async {
    print("first")
}

func second() {
   print("second")
}

func main() {
   Task {
      await first()
   }
   second()
}

main()

Even though marking first function as async does not make any sense as there is no async work it does, but still it is possible...

I was expecting that even though the first function is being awaited, it will be called asynchronously.

But actually the output is

first 
second

How would I call the fist function asynchronously mimicking the GCD's variant of:

DispatchQueue.current.async { first() }
second()

CodePudding user response:

The second task is actually not waiting for the first task, which runs on a separate thread, to finish. Actually do something time consuming in the first task and you will see the second task actually isn’t waiting at all.

Using Task { ... } is more to DispatchQueue.global().async { first() }. The first and second tasks are running on separate threads, so you simply have a race and you have no assurances as which order the print statements run. (In my tests, it runs second before first most of the time, but it can still occasionally run first before second.)

So, the question is, do you really care which order these two tasks start? If so, you can eliminate the race by (obviously) putting the Task { await first() } after the call to second. Or do you simply want to ensure that second won’t wait for first to finish? In that case, this already is the behavior and no change to your code is required.


You asked:

What if await first() needs to be run on the same queue as second() but asynchronously, is that possible with the Task api, or GCD would be needed to achieve that?

You can do this with modern concurrency API, by marking the routine to update the UI with @MainActor. But note, do not use this qualifier with time-consuming task, itself (because you do not want to block the main thread), but rather decouple the time-consuming operation from the UI update, and just mark the latter as @MainActor.

E.g., here is an example that manually calculates π asynchronously, and updates the UI when it is done:

func startCalculation() {
    Task {
        let pi = await calculatePi()
        updateWithResults(pi)
    }
    updateThatCalculationIsUnderway() // this really should go before the Task to eliminate any races, but just to illustrate that this second routine really does not wait
}

// deliberately inefficient calculation of pi

func calculatePi() async -> Double {
    var value: Double = 0
    var denominator: Double = 1
    var sign: Double = 1
    var increment: Double = 0

    repeat {
        increment = 4 / denominator
        value  = sign * 4 / denominator
        denominator  = 2
        sign *= -1
    } while increment > 0.000000001

    return value
}

func updateThatCalculationIsUnderway() {
    statusLabel.text = "Calculating π"
}

@MainActor
func updateWithResults(_ value: Double) {
    statusLabel.text = "Done"
    resultLabel.text = formatter.string(for: value)
}
  • Related