Execution Order of Awaited Tuples

While writing some tests, I noticed that this

await _ = (
  someAsyncFunc(),
  someAsyncFunc()
)

seems to be equivalent to

await someAsyncFunc()
await someAsyncFunc()

I.e. the second call is executed after the first call has completed.

I hadn't seen or tried the syntax of the first snippet before and my intuition was that it would execute the two calls concurrently; that it was a shorter way of writing this:

async let a: Void = someAsyncFunc()
async let b: Void = someAsyncFunc()
await _ = (a, b)

(but as stated above, it proved not to be.)

Is this correct and is it documented somewhere?

3 Likes

One Task can do One Job. This is the basic design of structured Concurrency.
If you want to do N things at the same time, you have to spawn N tasks.

single await is just run the job and suspend until done, thats all, does not make any extra Task.
async let is different, it spawns child Task using current context.
So async let can be used for known number of Concurrency.

Moreover, First syntax share same isolation by default, thus non escaping. Second async let is making childTask, so it can not access isolated state of current Task context.

This makes big different.

func foo() async {
    // current local value
    var count = 0

    let block = {
        await Task.yield()
        count += 1
    }
    // compiler, Concurrency knows no-race
    await (block(),block())
    
}


func bar() async {
    // current local value
    var count = 0

    let block = {
        await Task.yield()
        count += 1
    }
    // compiler, Concurrency detect data race
//Error: Access can happen concurrently
    async let job1:Void = block()
    async let job2:Void = block()
}

First time I've seen anyone putting the await on the left side of the assignment operator. I guess this works because assignment is also an expression*, but it looks strange to me.

* An assignment expression returns Void, by the way, and not the value on the rhs of the assignment: let x: Void = _ = 1

More or less in SE-0296: async/await:

An await operand may contain more than one potential suspension point. For example, we can use one await to cover both potential suspension points from our example by rewriting it as:

let (data, response) = try await session.dataTask(with: server.redirectURL(for: url))

The above is equivalent to:

let newURL = await server.redirectURL(for: url)
let (data, response) = try await session.dataTask(with: newURL)
6 Likes