Asynchronous operations in REPL

I am trying to use the REPL to perform some asynchronous operations (i.e. communicate with some servers, connect and retrieve data from databases, use "web-socket" like protocols, etc.).

I seem to be unable to get any simple asynchronous operation going. For example:

> var counter = 0
> DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) { counter += 1 }
// counter is always 0

I am not sure whether the problem is any of the following:

  • The REPL (as command-line programs) doesn't have a RunLoop.
  • The REPL stops execution completely and waits for more input lines.

I would appreciate if someone knows whether asynchronous operations are actually possible and if so, how.

You can run the main RunLoop for a short time (thus letting it drain the main queue):

RunLoop.main.run(until: Date(timeIntervalSinceNow: 2))
3 Likes

This does work, thank you @mayoff.
I find it a bit inconvenient to call runloop.run(until:) after any asynchronous operation, though. Is there any other possible option?

Use a playground in Xcode.

You could use a DispatchGroup

@clayellis Could you put an example?

The main problem I am facing is that it is impossible to have a long-running task, while still be able to use the REPL. The usage I have in mind is receiving continuous data from a server (e.g. every minute) while letting the user run swift commands on the stored data (e.g. filter and process rows in a SQLite database).

The solution propose by @mayoff for the REPL freezes the REPL; while playgrounds are not a good match since every time you add new code, the whole thing recompiles.

“The whole thing recompiles” only if you modify code that has already executed. If you only add more code at the end of the playground, then it incrementally executes the new code without re-executing the older code.

Oh great, I was not aware of that. I will try to get a playground running for long time and see how it works. In any case, thank you for the information @mayoff.

I am a bit disappointed, though that I cannot use the REPL for such purposes. The REPL and swift -F flag for custom frameworks have a great potential as an interactive command-line app.

1 Like

Also, if you click and hold on the small triangle "play" button in the divider between the console and the editor you can switch to Manually Run.

I'd recommend writing a Command Line app instead of just using the REPL (based on what you've shared you're trying to accomplish.)

Here's a really simple example of using a DispatchGroup to wait for an async method to finish before exiting the program.

import Foundation

let group = DispatchGroup()

func someAsyncMethod() {
    group.enter()
    DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
        print("done")
        group.leave()
    }
}

print("calling")
someAsyncMethod()
print("waiting")

group.notify(queue: .main) {
    print("exit")
    exit(EXIT_SUCCESS)
}

dispatchMain()

This will print:

calling
waiting
done
exit
Program ended with exit code: 0

I'd recommend reading up on DispatchGroup. It's not a terribly complex API but can be misused and frustrating if you aren't careful.

Great, thank you @clayellis; it is quite simple. I will keep it on my snippets. And yes, command-line will probably be the following steps.

Happy to help