Vladislav Brylinskiy
Vladislav Brylinskiy

Reputation: 549

How to call async function asynchronously without awaiting for the result

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()

Upvotes: 9

Views: 13674

Answers (2)

Dragonboh
Dragonboh

Reputation: 1

  1. Stop working with Concurrency in playground - minimum Simulator, better real device

  2. You code work correctly on Device and Simulator

prints :

second
first
  1. I can't reproduce you example always get: second print first

  2. STOP working with Concurrency with single Print statements. You can't guarantee how fast is your multicore device. What can you do to investigate how it is working:

  • Use (for in ) loops for 100 or 1000 elements and prints
  • Use Task.sleep or Thread.sleep ( for GCD ) to at least stop main thread and see if your first print will print first.

My code work exactly as DispatchQueue.current.async { first() }

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view.
    
    Task {
       await first()
    }
    second()
}

func first() async {
    for i in 0...1000 {
      print("🤍 \(i)")
    }
}

func second() {
    for i in 0...1000 {
      print("♥️ \(i)")
    }
}

Result will be first all Black hearts then all White.

If you what DispatchQueue.global.async. use Task.detached

But ATTENTION!!! Task {} - work like DispatchQueue.current.async, but on every await it can change Thread.

Upvotes: 0

Rob
Rob

Reputation: 437392

This behavior will change depending upon the context.

  • If you invoke this from a non-isolated context, then first and second will run on separate threads. In this scenario, the second task is not actually waiting for the first task, but rather there is a race as to which will finish first. This can be illustrated if you do something time-consuming in the first task and you will see the second task is not waiting at all.

    This introduces a race between first and second and you have no assurances as which order they will run. (In my tests, it runs second before first most of the time, but it can still occasionally run first before second.)

  • However, if you invoke this from an actor-isolated context, then first will wait for second to yield before running.

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. … I am just thinking [that if it runs on background thread that it] would mean crashes due to updates of UI not from the main thread.

You can mark the routine to update the UI with @MainActor, which will cause it to run on the main thread. But note, do not use this qualifier with the 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 {
    await Task.detached {
        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
    }.value
}

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

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

Note: To ensure this slow synchronous calculation of calculatePi is not run on the current actor (presumably the main actor), we want an “unstructured task”. Specifically, we want a “detached task”, i.e., one that is not run on the current actor. As the Unstructured Concurrency section of The Swift Programming Language: Concurrency: Tasks and Task Groups says:

To create an unstructured task that runs on the current actor, call the Task.init(priority:operation:) initializer. To create an unstructured task that’s not part of the current actor, known more specifically as a detached task, call the Task.detached(priority:operation:) class method.

Upvotes: 9

Related Questions