swiftasync-awaittaskcommand-line-tool

async/await usage inside console app in macOS


I’m quite new to async/await concept in Swift. So I’ve developed a small playground for myself:

import Foundation

func dummyLoop() async {
    print("inside dummy fcn")
    for i in 0 ..< 10 {
        if Task.isCancelled {
            return
        }
        print("Loop \(i)")
        try? await Task.sleep(for: .seconds(1))
    }
}

let task = Task(priority: .high) {
    await dummyLoop()
}

print("Press any key to stop task...")
_ = readLine()
task.cancel()
print("Press any key to stop finish app...")
_ = readLine()
print("Ciao")

So I want to run some dummy loop and stop it after pressing Enter in console. But I see that task closure is not executed at all. I figured out that this behaviour is somehow related to RunLoop... Can somebody explain what is going on here? And how should I run a Task to be executed in parallel in console app?


Solution

  • Task.init inherits the actor context, which in this case is the main actor. The main actor runs on the main thread, but the main thread is blocked by readLine, so nothing gets done.

    You can use Task.detached instead, which runs the task on some thread from the cooperative thread pool.

    let task = Task.detached(priority: .high) {
        await dummyLoop()
    }
    

    Alternatively, you can read from stdin asynchronously, instead of using readLine which blocks.

    var iterator = FileHandle.standardInput.bytes.lines.makeAsyncIterator()
    print("Press any key to stop task...")
    _ = try await iterator.next()
    task.cancel()
    print("Press any key to stop finish app...")
    _ = try await iterator.next()
    print("Ciao")
    

    In your print messages you mentioned "press any key...", but neither readLine nor reading lines asynchronously achieves that. In either case the program will wait for the return key. To detect any key press, you can use enableRawMode from this post.