Swift question: there is a library that has some functionality, but it's implemented as an actor. I'm working a a completely synchronous program (a CLI app that builds a static website), but I want to use a function from this actor from normal non-concurrent code.

In the example code for using the library it awaits a function call. Is it possible to call this using some kind of concurrent-in-non-concurrent wrapper?

The library in question is MarkdownSyntax, and I suppose it's implemented as an actor for performance reasons in a UI context (do stuff in the background I presume?).

#swiftlang #askfedi

@torb I’m not sure I understand the question. But a CLI app can use async/await. What’s the real blocker?

@nixzhu Yeah, but the entire rest of my app is synchronous (including some libraries that I'm not sure that play nice with async code).

That said, I'm fairly new to coding in Swift, so there's a good chance I'm not understanding and it's possible in ways that I'm not aware of!

@nixzhu Thanks for answering still!

@torb You can have an async entrypoint. You can also just call RunLoop.main.run() or dispatchMain() and then call exit(0) from other places in your program.

There are some bad ways to wait in synchronous context, like DispatchSemaphore and DispatchGroup.

Example of async entrypoint: https://codeberg.org/Cyberbeni/auth.home.arpa/src/branch/master/Sources/auth_home_arpa/Entrypoint.swift

auth.home.arpa/Sources/auth_home_arpa/Entrypoint.swift at master

auth.home.arpa - Minimal forward_auth service for Caddy and other reverse proxies.

Codeberg.org

@Cyberbeni Yeah, that seemed like the only possible way to do it, I just was wondering if I could somehow do this without function colouring my app to async… but it makes sense that it has to be this way I suppose.

Well, if nothing else, I guess this is a nice excuse to actually make my app do more work in parallel.

@Cyberbeni Thank so much for answering by the way!
@torb from out of my head, wrap the call into Task { … } and then use a (think) semaphore (setup before the task) to wait for the tasks completion.

@V_ Oh interesting. I'd love to not have to make my app async only for this specific part.

Oh, this is the “bad” way @Cyberbeni warned me about. Oh well, this is just for my personal static site generator, I already program it somewhat unconventional ways.

But thanks!

@torb I’ve seen that @mortenbekditlevsen has posted a complete example, that was what I tried to explain (from head) yesterday. Do you have more details about the warning you got? I use this style of code myself as well - not that I have a bug in it and don’t know it.

Btw I’m not sure if working about having something async in an otherwise non async codebase would be a big deal. At least I find it a lot less intrusive when you can use await’s instead of callbacks.

@torb You can always call into asynchronous code from synchronous code by using a semaphore from GCD to block while in the async call.
Like this:
```
func fetchSync() -> String {
let semaphore = DispatchSemaphore(value: 0)
var result = ""

Task {
let (data, _) = try await URLSession.shared.data(from: URL(string: "…")!)
result = String(data: data, encoding: .utf8) ?? ""
semaphore.signal()
}

semaphore.wait()
return result
}
```

@mortenbekditlevsen This is very close to what I ended up doing, inspired by both by comments of @V_, @Cyberbeni and this post on the Swift Forums: https://forums.swift.org/t/using-async-functions-from-synchronous-functions-and-breaking-all-the-rules/59782/4 ).

I ended up just making a version specific for my needs instead of making a generic one (among other things: my program always exits immediately upon any error so catching throws is not needed).

And it seems to work well!

Thanks for the help everyone!

Using `async` functions from synchronous functions (and breaking all the rules)

You can use a class to box the returned value. Something like this works (with all the safety caveats mentioned): fileprivate class Box<ResultType> { var result: Result<ResultType, Error>? = nil } /// Unsafely awaits an async function from a synchronous context. @available(*, deprecated, message: "Migrate to structured concurrency") func _unsafeWait<ResultType>(_ f: @escaping () async throws -> ResultType) throws -> ResultType { let box = Box<ResultType>() let sema = DispatchSemaphore(value...

Swift Forums