16

WWDC21 introduces Swift 5.5, with async/await. Following the Explore structured concurrency in Swift and Meet async/await in Swift WWDC21 sessions, I'm trying to use the async let function.

Here's my Playground code:

func doIt() async -> String {
    let t = TimeInterval.random(in: 0.25 ... 2.0)
    Thread.sleep(forTimeInterval: t)
    return String("\(Double.random(in: 0...1000))")
}

async {
    async let a = doIt()
    async let b = doIt()
    async let c = doIt()
    async let d = doIt()
    
    let results = await [a, b, c, d]
    for result in results {
        print("  \(result)")
    }
}

However, for each of the "async let" lines, I get this error:

error: AsyncLetSwift55WWDC21.playground:12:15: error: expression is 'async' but is not marked with 'await'
    async let a = doIt()
              ^
              await 

Paul Hudson's blog showed this example: Hacking With Swift async let example

The Exploring structured currency video has this example at about the 8:10 mark: enter image description here

EDIT: This does seem to be a Playground-specific issue. Per the suggestion on the same issue in Apple Developer Forums, running the same code (ok, I did add sleep(10) to the end of the source file after the async block for macOS, so the app wouldn't terminate before the async calls completed) as a macOS command-line project gives no errors and produces the proper output.

Is this a bug, or am I just not understanding something?

drewster
  • 5,460
  • 5
  • 40
  • 50

3 Answers3

8

My advice would be: don't try this in a playground. Playgrounds aren't ready for this stuff yet. Your code compiles and runs fine in a real project. Here's an example:

class ViewController: UIViewController {
    
    func doIt() async -> String {
        let t = TimeInterval.random(in: 0.25 ... 2.0)
        Thread.sleep(forTimeInterval: t)
        return String("\(Double.random(in: 0...1000))")
    }
    func test() {
        async {
            async let a = doIt()
            async let b = doIt()
            async let c = doIt()
            async let d = doIt()
            
            let results = await [a, b, c, d]
            for result in results {
                print("  \(result)")
            }
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        test()
    }
}
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • Thanks, Matt. I started testing this today and was getting a lot of crazy errors in Playground. One question. You use Thread.sleep() but the specification recommends the new Task.sleep(). The problem is that Task.sleep() doesn't work right now. Could you make it work? https://docs.swift.org/swift-book/LanguageGuide/Concurrency.html – Mikiko Jane Jun 14 '21 at 00:53
  • 2
    `Task.sleep` currently works only for very small values as far as I can tell. Not sure what's up with that. – matt Jun 14 '21 at 01:59
  • 1
    Since Task.sleep doesn't work as af now, as a workaround you could use `withCheckedContinuation` inside that you could invoke `DispatchQueue.main.asyncAfter` – user1046037 Jul 03 '21 at 05:43
  • @user1046037 Absolutely! See my https://www.biteinteractive.com/swift-5-5-asynchronous-looping-with-async-await/ for an example. – matt Jul 03 '21 at 07:24
4

Building as a macOS command line project (Xcode: File -> New -> Project, then select "Command Line Tool" from the macOS tab), the code works perfectly. (This was a suggestion from a response in the Apple Developer Forums.)

I've added a single sleep(10) to the end so that the command line tool doesn't exit before the async calls finish:

import Foundation

print("Hello, world!")

func doIt() async -> String {
    let t = TimeInterval.random(in: 0.25 ... 2.0)
    Thread.sleep(forTimeInterval: t)
    return String("\(Double.random(in: 0...1000))")
}

async {
    async let a = doIt()
    async let b = doIt()
    async let c = doIt()
    async let d = doIt()
    
    let results = await [a, b, c, d]
    for result in results {
        print("  \(result)")
    }
}
sleep(10)

This produces the expected sort of console output (Note: the actual values will differ on every run)*:

Hello, World!
  415.407747869283
  574.28639828183
  689.4706625185836
  385.56539085197113
Program ended with exit code: 0
drewster
  • 5,460
  • 5
  • 40
  • 50
-4

It runs well as macOS command line project, but when I created it as a iOS project,

Those codes:

async let a = doIt()
async let b = doIt()
async let c = doIt()
async let d = doIt()

DO NOT runs in parallel.

  • Note that if you are running on real hardware, the task pool for async/await can be very small, and is entirely cooperative. That means if you create an async task that does CPU work without awaiting or yielding, it will hog one of the very few available threads in the pool. That means that only a few of your tasks will actually run. – Dag Ågren Mar 28 '23 at 11:25